Robot Framework Integrated Development Environment (RIDE)
publisher.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 import sys
17 import inspect
18 import types
19 from pubsub import pub
20 from typing import Type, Callable
21 from ..publish.messages import RideMessage
22 
23 
24 class _Publisher:
25 
26  def __init__(self):
27  self._publisher_publisher = pub.getDefaultPublisher()
28  self._publisher_publisher.setListenerExcHandler(ListenerExceptionHandler())
29 
30  @staticmethod
31  def _get_topic(topic_cls: Type[RideMessage]) -> str:
32  if inspect.isclass(topic_cls) and issubclass(topic_cls, RideMessage):
33  return topic_cls.topic()
34  raise TypeError('Expected topic type {}, actual {}.'.format(RideMessage, topic_cls))
35 
36  @staticmethod
37  def _validate_listener(listener: Callable):
38  sig = inspect.signature(listener)
39  params = sig.parameters
40  error_msg = 'only 1 required param (message) is expected.'
41  assert len(params) == 1, 'Too many listener params, ' + error_msg
42  assert str(list(params.values())[0]) in ['message', 'data'], 'Invalid listener param, ' + error_msg
43 
44 
45  def subscribe(self, listener: Callable, topic: Type[RideMessage]):
46  self._validate_listener_validate_listener(listener)
47  self._publisher_publisher.subscribe(listener, self._get_topic_get_topic(topic))
48 
49 
50  def publish(self, topic: Type[RideMessage], message):
51  self._publisher_publisher.sendMessage(self._get_topic_get_topic(topic), message=message)
52 
53  def unsubscribe(self, listener: Callable, topic: Type[RideMessage]):
54  self._publisher_publisher.unsubscribe(listener, self._get_topic_get_topic(topic))
55 
56 
66  def unsubscribe_all(self, obj=None):
67 
68  def _listener_filter(listener):
69 
72  _callable = listener.getCallable()
73  functions = [func for _, func in _get_members_safely(obj, inspect.isfunction)]
74  methods = [method for _, method in _get_members_safely(obj, inspect.ismethod)]
75  if _callable in functions or _callable in methods:
76  return True
77 
78 
81  _listener_filter = _listener_filter if obj is not None else None
82  self._publisher_publisher.unsubAll(listenerFilter=_listener_filter)
83 
84 
85 class ListenerExceptionHandler(pub.IListenerExcHandler):
86 
87  def __call__(self, listenerID: str, topicObj: pub.Topic):
88  from .messages import RideLogException
89  topic_name = topicObj.getName()
90  if topic_name != RideLogException.topic():
91  error_msg = 'Error in listener: {}, topic: {}'.format(listenerID, topic_name)
92  log_message = RideLogException(message=error_msg,
93  exception=None, level='ERROR')
94  sys.stderr.write(log_message.__getattribute__('message'))
95  log_message.publish()
96 
97 
98 
107 def _get_members_safely(obj, predicate=None):
108  if inspect.isclass(obj):
109  mro = (obj,) + inspect.getmro(obj)
110  else:
111  mro = ()
112  results = []
113  processed = set()
114  names = dir(obj)
115  # :dd any DynamicClassAttributes to the list of names if object is a class;
116  # this may result in duplicate entries if, for example, a virtual
117  # attribute with the same name as a DynamicClassAttribute exists
118  try:
119  for base in obj.__bases__:
120  for k, v in base.__dict__.items():
121  if isinstance(v, types.DynamicClassAttribute):
122  names.append(k)
123  except AttributeError:
124  pass
125  for key in names:
126  # First try to get the value via getattr. Some descriptors don't
127  # like calling their __get__ (see bug #1785), so fall back to
128  # looking in the __dict__.
129  try:
130  value = getattr(obj, key)
131  # handle the duplicate key
132  if key in processed:
133  raise AttributeError
134  except Exception as e:
135  """ UPDATED HERE: Catch all types of exceptions. """
136  if isinstance(e, AttributeError):
137  """ UPDATED HERE: Use old logic if exception is AttributeError. """
138  for base in mro:
139  if key in base.__dict__:
140  value = base.__dict__[key]
141  break
142  else:
143  # could be a (currently) missing slot member, or a buggy
144  # __dir__; discard and move on
145  continue
146  else:
147  """ UPDATED HERE: Ignore this attribute when other types of exception raised. """
148  continue
149  if not predicate or predicate(value):
150  results.append((key, value))
151  processed.add(key)
152  results.sort(key=lambda pair: pair[0])
153  return results
154 
155 
156 """Global `Publisher` instance for subscribing to and unsubscribing from RideMessages."""
157 PUBLISHER = _Publisher()
This class represents a general purpose log message with a traceback appended to message text.
Definition: messages.py:117
def __call__(self, str listenerID, pub.Topic topicObj)
Definition: publisher.py:87
str _get_topic(Type[RideMessage] topic_cls)
Definition: publisher.py:31
def subscribe(self, Callable listener, Type[RideMessage] topic)
The listener's param signature must be (message)
Definition: publisher.py:45
def publish(self, Type[RideMessage] topic, message)
All subscribed listeners' param signatures have been guaranteed.
Definition: publisher.py:50
def unsubscribe(self, Callable listener, Type[RideMessage] topic)
Definition: publisher.py:53
def unsubscribe_all(self, obj=None)
If the given object's:
Definition: publisher.py:66
def _validate_listener(Callable listener)
Definition: publisher.py:37
def _get_members_safely(obj, predicate=None)
Return all members of an object as (name, value) pairs sorted by name.
Definition: publisher.py:107