Total Complexity | 64 |
Total Lines | 309 |
Duplicated Lines | 8.74 % |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like asyncua.common.events often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | import copy |
||
2 | |||
3 | from asyncua import ua |
||
4 | import asyncua |
||
5 | from ..ua.uaerrors import UaError |
||
6 | from .ua_utils import get_node_subtypes |
||
7 | |||
8 | |||
9 | class Event: |
||
10 | """ |
||
11 | OPC UA Event object. |
||
12 | This is class in inherited by the common event objects such as BaseEvent, |
||
13 | other auto standard events and custom events |
||
14 | Events are used to trigger events on server side and are |
||
15 | sent to clients for every events from server |
||
16 | |||
17 | Developer Warning: |
||
18 | On server side the data type of attributes should be known, thus |
||
19 | add properties using the add_property method!!! |
||
20 | """ |
||
21 | |||
22 | def __init__(self, emitting_node=ua.ObjectIds.Server): |
||
23 | self.server_handle = None |
||
24 | self.select_clauses = None |
||
25 | self.event_fields = None |
||
26 | self.data_types = {} |
||
27 | self.emitting_node = emitting_node |
||
28 | if isinstance(emitting_node, ua.NodeId): |
||
29 | self.emitting_node = emitting_node |
||
30 | else: |
||
31 | self.emitting_node = ua.NodeId(emitting_node) |
||
32 | # save current attributes |
||
33 | self.internal_properties = list(self.__dict__.keys())[:] + ["internal_properties"] |
||
34 | |||
35 | def __str__(self): |
||
36 | return "{0}({1})".format( |
||
37 | self.__class__.__name__, |
||
38 | [str(k) + ":" + str(v) for k, v in self.__dict__.items() if k not in self.internal_properties]) |
||
39 | __repr__ = __str__ |
||
40 | |||
41 | def add_property(self, name, val, datatype): |
||
42 | """ |
||
43 | Add a property to event and store its data type |
||
44 | """ |
||
45 | setattr(self, name, val) |
||
46 | self.data_types[name] = datatype |
||
47 | |||
48 | def add_variable(self, name, val, datatype): |
||
49 | """ |
||
50 | Add a variable to event and store its data type |
||
51 | variables are able to have properties as children |
||
52 | """ |
||
53 | setattr(self, name, val) |
||
54 | self.data_types[name] = datatype |
||
55 | |||
56 | def get_event_props_as_fields_dict(self): |
||
57 | """ |
||
58 | convert all properties and variables of the Event class to a dict of variants |
||
59 | """ |
||
60 | field_vars = {} |
||
61 | for key, value in vars(self).items(): |
||
62 | if not key.startswith("__") and key not in self.internal_properties: |
||
63 | field_vars[key] = ua.Variant(value, self.data_types[key]) |
||
64 | return field_vars |
||
65 | |||
66 | @staticmethod |
||
67 | def from_field_dict(fields): |
||
68 | """ |
||
69 | Create an Event object from a dict of name and variants |
||
70 | """ |
||
71 | ev = Event() |
||
72 | for k, v in fields.items(): |
||
73 | ev.add_property(k, v.Value, v.VariantType) |
||
74 | return ev |
||
75 | |||
76 | def to_event_fields_using_subscription_fields(self, select_clauses): |
||
77 | """ |
||
78 | Using a new select_clauses and the original select_clauses |
||
79 | used during subscription, return a field list |
||
80 | """ |
||
81 | fields = [] |
||
82 | for sattr in select_clauses: |
||
83 | for idx, o_sattr in enumerate(self.select_clauses): |
||
84 | if sattr.BrowsePath == o_sattr.BrowsePath and sattr.AttributeId == o_sattr.AttributeId: |
||
85 | fields.append(self.event_fields[idx]) |
||
86 | break |
||
87 | return fields |
||
88 | |||
89 | def to_event_fields(self, select_clauses): |
||
90 | """ |
||
91 | return a field list using a select clause and the object properties |
||
92 | """ |
||
93 | fields = [] |
||
94 | for sattr in select_clauses: |
||
95 | if not sattr.BrowsePath: |
||
96 | name = ua.AttributeIds(sattr.AttributeId).name |
||
97 | else: |
||
98 | name = sattr.BrowsePath[0].Name |
||
99 | iter_paths = iter(sattr.BrowsePath) |
||
100 | next(iter_paths) |
||
101 | for path in iter_paths: |
||
102 | name += '/' + path.Name |
||
103 | try: |
||
104 | val = getattr(self, name) |
||
105 | except AttributeError: |
||
106 | field = ua.Variant(None) |
||
107 | else: |
||
108 | if val is None: |
||
109 | field = ua.Variant(None) |
||
110 | else: |
||
111 | field = ua.Variant(copy.deepcopy(val), self.data_types[name]) |
||
112 | fields.append(field) |
||
113 | return fields |
||
114 | |||
115 | @staticmethod |
||
116 | def from_event_fields(select_clauses, fields): |
||
117 | """ |
||
118 | Instantiate an Event object from a select_clauses and fields |
||
119 | """ |
||
120 | ev = Event() |
||
121 | ev.select_clauses = select_clauses |
||
122 | ev.event_fields = fields |
||
123 | for idx, sattr in enumerate(select_clauses): |
||
124 | if len(sattr.BrowsePath) == 0: |
||
125 | name = sattr.AttributeId.name |
||
126 | else: |
||
127 | name = sattr.BrowsePath[0].Name |
||
128 | iter_paths = iter(sattr.BrowsePath) |
||
129 | next(iter_paths) |
||
130 | for path in iter_paths: |
||
131 | name += '/' + path.Name |
||
132 | ev.add_property(name, fields[idx].Value, fields[idx].VariantType) |
||
133 | return ev |
||
134 | |||
135 | |||
136 | async def get_filter_from_event_type(eventtypes): |
||
137 | evfilter = ua.EventFilter() |
||
138 | evfilter.SelectClauses = await select_clauses_from_evtype(eventtypes) |
||
139 | evfilter.WhereClause = await where_clause_from_evtype(eventtypes) |
||
140 | return evfilter |
||
141 | |||
142 | |||
143 | async def select_clauses_from_evtype(evtypes): |
||
144 | clauses = [] |
||
145 | selected_paths = [] |
||
146 | for evtype in evtypes: |
||
147 | event_props_and_vars = await get_event_properties_from_type_node(evtype) |
||
148 | event_props_and_vars.extend(await get_event_variables_from_type_node(evtype)) |
||
149 | for node in event_props_and_vars: |
||
150 | browse_name = await node.read_browse_name() |
||
151 | if browse_name not in selected_paths: |
||
152 | op = ua.SimpleAttributeOperand() |
||
153 | op.AttributeId = ua.AttributeIds.Value |
||
154 | op.BrowsePath = [browse_name] |
||
155 | clauses.append(op) |
||
156 | selected_paths.append(browse_name) |
||
157 | for var in await get_event_variables_from_type_node(evtype): |
||
158 | browse_name = await var.get_browse_name() |
||
159 | if browse_name not in selected_paths: |
||
160 | op = ua.SimpleAttributeOperand() |
||
161 | op.AttributeId = ua.AttributeIds.Value |
||
162 | op.BrowsePath = [browse_name] |
||
163 | clauses.append(op) |
||
164 | selected_paths.append(browse_name) |
||
165 | for prop in await var.get_properties(): |
||
166 | browse_path = [browse_name, await prop.get_browse_name()] |
||
167 | if browse_path not in selected_paths: |
||
168 | op = ua.SimpleAttributeOperand() |
||
169 | op.AttributeId = ua.AttributeIds.Value |
||
170 | op.BrowsePath = browse_path |
||
171 | clauses.append(op) |
||
172 | selected_paths.append(browse_path) |
||
173 | return clauses |
||
174 | |||
175 | |||
176 | async def where_clause_from_evtype(evtypes): |
||
177 | cf = ua.ContentFilter() |
||
178 | el = ua.ContentFilterElement() |
||
179 | # operands can be ElementOperand, LiteralOperand, AttributeOperand, SimpleAttribute |
||
180 | # Create a clause where the generate event type property EventType |
||
181 | # must be a subtype of events in evtypes argument |
||
182 | |||
183 | # the first operand is the attribute event type |
||
184 | op = ua.SimpleAttributeOperand() |
||
185 | # op.TypeDefinitionId = evtype.nodeid |
||
186 | op.BrowsePath.append(ua.QualifiedName("EventType", 0)) |
||
187 | op.AttributeId = ua.AttributeIds.Value |
||
188 | el.FilterOperands.append(op) |
||
189 | # now create a list of all subtypes we want to accept |
||
190 | subtypes = [] |
||
191 | for evtype in evtypes: |
||
192 | for st in await get_node_subtypes(evtype): |
||
193 | subtypes.append(st.nodeid) |
||
194 | subtypes = list(set(subtypes)) # remove duplicates |
||
195 | for subtypeid in subtypes: |
||
196 | op = ua.LiteralOperand() |
||
197 | op.Value = ua.Variant(subtypeid) |
||
198 | el.FilterOperands.append(op) |
||
199 | el.FilterOperator = ua.FilterOperator.InList |
||
200 | cf.Elements.append(el) |
||
201 | return cf |
||
202 | |||
203 | |||
204 | View Code Duplication | async def get_event_variables_from_type_node(node): |
|
|
|||
205 | variables = [] |
||
206 | curr_node = node |
||
207 | while True: |
||
208 | variables.extend(await curr_node.get_variables()) |
||
209 | if curr_node.nodeid.Identifier == ua.ObjectIds.BaseEventType: |
||
210 | break |
||
211 | parents = await curr_node.get_referenced_nodes( |
||
212 | refs=ua.ObjectIds.HasSubtype, direction=ua.BrowseDirection.Inverse, includesubtypes=True) |
||
213 | if len(parents) != 1: # Something went wrong |
||
214 | return None |
||
215 | curr_node = parents[0] |
||
216 | return variables |
||
217 | |||
218 | |||
219 | async def get_event_properties_from_type_node(node): |
||
220 | properties = [] |
||
221 | curr_node = node |
||
222 | while True: |
||
223 | properties.extend(await curr_node.get_properties()) |
||
224 | if curr_node.nodeid.Identifier == ua.ObjectIds.BaseEventType: |
||
225 | break |
||
226 | parents = await curr_node.get_referenced_nodes( |
||
227 | refs=ua.ObjectIds.HasSubtype, direction=ua.BrowseDirection.Inverse, includesubtypes=True |
||
228 | ) |
||
229 | if len(parents) != 1: # Something went wrong |
||
230 | return None |
||
231 | curr_node = parents[0] |
||
232 | return properties |
||
233 | |||
234 | |||
235 | View Code Duplication | async def get_event_variables_from_type_node(node): |
|
236 | variables = [] |
||
237 | curr_node = node |
||
238 | while True: |
||
239 | variables.extend(await curr_node.get_variables()) |
||
240 | if curr_node.nodeid.Identifier == ua.ObjectIds.BaseEventType: |
||
241 | break |
||
242 | parents = await curr_node.get_referenced_nodes( |
||
243 | refs=ua.ObjectIds.HasSubtype, direction=ua.BrowseDirection.Inverse, includesubtypes=True |
||
244 | ) |
||
245 | if len(parents) != 1: # Something went wrong |
||
246 | return None |
||
247 | curr_node = parents[0] |
||
248 | return variables |
||
249 | |||
250 | |||
251 | async def get_event_obj_from_type_node(node): |
||
252 | """ |
||
253 | return an Event object from an event type node |
||
254 | """ |
||
255 | if node.nodeid.NamespaceIndex == 0 and \ |
||
256 | node.nodeid.Identifier in asyncua.common.event_objects.IMPLEMENTED_EVENTS.keys(): |
||
257 | return asyncua.common.event_objects.IMPLEMENTED_EVENTS[node.nodeid.Identifier]() |
||
258 | else: |
||
259 | parent_identifier, parent_eventtype = await _find_parent_eventtype(node) |
||
260 | |||
261 | class CustomEvent(parent_eventtype): |
||
262 | |||
263 | def __init__(self): |
||
264 | parent_eventtype.__init__(self) |
||
265 | self.EventType = node.nodeid |
||
266 | |||
267 | async def init(self): |
||
268 | curr_node = node |
||
269 | while curr_node.nodeid.Identifier != parent_identifier: |
||
270 | for prop in await curr_node.get_properties(): |
||
271 | name = (await prop.read_browse_name()).Name |
||
272 | val = await prop.read_data_value() |
||
273 | self.add_property(name, val.Value.Value, val.Value.VariantType) |
||
274 | for var in await curr_node.get_variables(): |
||
275 | name = (await var.read_browse_name()).Name |
||
276 | val = await var.read_data_value() |
||
277 | self.add_variable(name, val.Value.Value, await var.read_data_type_as_variant_type()) |
||
278 | for prop in await var.get_properties(): |
||
279 | prop_name = (await prop.read_browse_name()).Name |
||
280 | name = '%s/%s' % (name, prop_name) |
||
281 | val = await prop.read_data_value() |
||
282 | self.add_property(name, val.Value.Value, val.Value.VariantType) |
||
283 | parents = await curr_node.get_referenced_nodes(refs=ua.ObjectIds.HasSubtype, |
||
284 | direction=ua.BrowseDirection.Inverse, |
||
285 | includesubtypes=True) |
||
286 | if len(parents) != 1: # Something went wrong |
||
287 | raise UaError("Parent of event type could not be found") |
||
288 | curr_node = parents[0] |
||
289 | self._freeze = True |
||
290 | |||
291 | ce = CustomEvent() |
||
292 | await ce.init() |
||
293 | return ce |
||
294 | |||
295 | |||
296 | async def _find_parent_eventtype(node): |
||
297 | """ |
||
298 | """ |
||
299 | parents = await node.get_referenced_nodes(refs=ua.ObjectIds.HasSubtype, direction=ua.BrowseDirection.Inverse, |
||
300 | includesubtypes=True) |
||
301 | |||
302 | if len(parents) != 1: # Something went wrong |
||
303 | raise UaError("Parent of event type could not be found") |
||
304 | if parents[0].nodeid.NamespaceIndex == 0: |
||
305 | if parents[0].nodeid.Identifier in asyncua.common.event_objects.IMPLEMENTED_EVENTS.keys(): |
||
306 | return parents[0].nodeid.Identifier, \ |
||
307 | asyncua.common.event_objects.IMPLEMENTED_EVENTS[parents[0].nodeid.Identifier] |
||
308 | return await _find_parent_eventtype(parents[0]) |
||
309 |