| 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.read_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.read_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 |