Completed
Push — master ( 6fd567...f65269 )
by Daniel
90:27 queued 15s
created

SignalsPlugin.method()   A

Complexity

Conditions 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
dl 0
loc 5
rs 9.4285
1
"""
2
    gw_base_pattern provides all basic classes and functions, which are needed by any kind of groundwork plugin or
3
    pattern.
4
5
    It mostly cares about the correct activation and deactivation. Including sending signals to inform
6
    other patterns or plugins about status changes of a plugin.
7
8
"""
9
import logging
10
from .exceptions import PluginAttributeMissing, PluginActivateMissing, PluginDeactivateMissing
11
12
13
class GwBasePattern(object):
14
    """
15
    Base pattern class for all plugins and patterns.
16
17
    Usage::
18
19
        from groundwork.patterns import GwBasePattern
20
21
        class MyPlugin(GwBasePattern):
22
            def __init__(self, *args, **kwargs):
23
                super().__init__(*args, **kwargs)
24
25
            def activate():
26
                self.signals.register("MySignal", "My description about signal)
27
28
            def deactivate():
29
                self.signals.unregister("MySignal")
30
31
    :param app: groundwork application object, for which the plugin shall be initialised.
32
    :type app: :class:`groundwork.App`.
33
    :param name: Unique name. Normally set by plugin.
34
    """
35
    def __init__(self, app, *args, name=None, **kwargs):
36
        #: groundwork application instance. Access it inside a plugin via ``self.app``.
37
        self.app = app
38
39
        # There must be a name for this plugin. Otherwise it is not detectable and manageable on application level
40
        if not hasattr(self, "name"):
41
            raise PluginAttributeMissing("Name not set for plugin")
42
43
        # Let's be sure active is false, even if a child class set something different
44
        if not hasattr(self, "active"):
45
            self.active = False
46
47
        #: A logger, especially created for this plugin. Usage inside a plugin: ``self.log.warn("WARNING!!")``.
48
        #:
49
        #: The logger name is the same as the plugin name. Therefor it is possible to configure the application logging
50
        #: to show log messages of a specif plugin only. See :ref:`plugin_logging`
51
        self.log = logging.getLogger(self.name)
52
53
        #: Instance of :class:`.SignalsPlugin`.
54
        #: Provides functions to register and manage signals and retrievers.
55
        #:
56
        #: All action takes place in the context of this plugin. For instance a ``self.signals.get()`` will return
57
        #: signals of this plugin only. To get all signals of an application, please use ``self.app.signals.get()``.
58
        self.signals = SignalsPlugin(self)
59
60
        # This is used as flag for the pluginManager to be sure, that an initiated class has called the __init__()
61
        # routine of GwPluginPatter
62
        self._plugin_base_initialised = True
63
64
        # Let's tell the pluginManager that this plugin got initialised, so that it gets tracked on app level.
65
        # This is needed if this class gets initiated by hand and the function self.app.plugins._load() was not used
66
        # for doing this job.
67
        self.app.plugins._register_load(self)
68
69
    # def __getattribute__(self, name):
70
    def __getattribute__(self, name):
71
        """
72
        Catches all calls on class attributes, but care only for activate() and deactivate().
73
74
        If the plugin gets activated or deactivated, the base class can perform extra work.
75
        So there is no need for a plugin developer to call something like super().activate() for
76
        his/her plugin. This gets done automatically.
77
        """
78
79
        attr = object.__getattribute__(self, name)
80
        if hasattr(attr, '__call__'):
81
            if attr.__name__ == "activate":
82
                def newfunc(*args, **kwargs):
83
                    self._pre_activate_injection()
84
                    result = attr(*args, **kwargs)
85
                    self._post_activate_injection()
86
                    return result
87
                return newfunc
88
            elif attr.__name__ == "deactivate":
89
                def newfunc(*args, **kwargs):
90
                    self._pre_deactivate_injection()
91
                    result = attr(*args, **kwargs)
92
                    self._post_deactivate_injection()
93
                    return result
94
                return newfunc
95
            else:
96
                return attr
97
        else:
98
            return attr
99
100
    def activate(self):
101
        """
102
        Must be overwritten by the plugin class itself.
103
        """
104
        self.log.warn("No activation routine in Plugin defined. Define self.activate() in plugin %s" % self.name)
105
        raise PluginActivateMissing("plugin must provide an activation routine by itself.")
106
107
    def deactivate(self):
108
        """
109
        Must be overwritten by the plugin class itself.
110
        """
111
        self.log.warn("No activation routine in Plugin defined. Define self.deactivate() in plugin %s" % self.name)
112
        raise PluginDeactivateMissing("plugin must provide an deactivation routine by itself.")
113
114
    def _pre_activate_injection(self):
115
        """
116
        Injects functions before the activation routine of child classes gets called
117
        """
118
        # Let's be sure that this plugins class is registered and available on application level under
119
        # application.plugins.classes. This allows to reuse this class for *new* plugins.
120
        if not self.app.plugins.classes.exist(self.__class__.__name__):
121
            self.app.plugins.classes.register([self.__class__])
122
123
        self.app.signals.send("plugin_activate_pre", self)
124
125
    def _post_activate_injection(self):
126
        """
127
        Injects functions after the activation routine of child classes got called
128
        :return: None
129
        """
130
        self.active = True
131
        self.app.signals.send("plugin_activate_post", self)
132
133
    def _pre_deactivate_injection(self):
134
        """
135
        Injects functions before the deactivation routine of child classes gets called
136
        :return: None
137
        """
138
        self.app.signals.send("plugin_deactivate_pre", self)
139
140
    def _post_deactivate_injection(self):
141
        """
142
        Injects functions after the deactivation routine of child classes got called
143
        :return: None
144
        """
145
        # Lets be sure that active is really set to false.
146
        self.active = False
147
        self.app.signals.send("plugin_deactivate_post", self)
148
        # After all receivers are handled. We start to clean up signals and receivers of this plugin
149
        # Attention: This signal clean must not be called via a signal (like in other patterns),
150
        # because the call order of receivers is not clear and a signal/receiver clean up would prohibit the call
151
        # of all "later" receivers.
152
        self.signals.deactivate_plugin_signals()
153
154
155
class SignalsPlugin:
156
    """
157
    Signal and Receiver management class on plugin level.
158
    This class gets initiated once per plugin.
159
160
    Mostly delegates function calls to the :class:`groundwork.signals.SignalListApplication` instance on application
161
    level.
162
163
    :param plugin: The plugin, which wants to use signals
164
    :type plugin: GwBasePattern
165
    """
166
167
    def __init__(self, plugin):
168
        self._plugin = plugin
169
        self.__app = plugin.app
170
        self.__log = plugin.log
171
        self.__log.info("Plugin messages initialised")
172
173
    def deactivate_plugin_signals(self):
174
        receivers = self.get_receiver()
175
        for receiver in receivers.keys():
176
            self.disconnect(receiver)
177
178
        signals = self.get()
179
        for signal in signals:
180
            self.unregister(signal)
181
182
    def register(self, signal, description):
183
        """
184
        Registers a new signal.
185
        Only registered signals are allowed to be send.
186
187
        :param signal: Unique name of the signal
188
        :param description: Description of the reason or use case, why this signal is needed.
189
                            Used for documentation.
190
        """
191
        return self.__app.signals.register(signal, self._plugin, description)
192
193
    def unregister(self, signal):
194
        return self.__app.signals.unregister(signal)
195
196
    def connect(self, receiver, signal, function, description, sender=None):
197
        """
198
        Connect a receiver to a signal
199
200
        :param receiver: Name of the receiver
201
        :type receiver: str
202
        :param signal: Name of the signal. Must already be registered!
203
        :type signal: str
204
        :param function: Callable functions, which shall be executed, of signal is send.
205
        :param description: Description of the reason or use case, why this connection is needed.
206
                            Used for documentation.
207
        """
208
        return self.__app.signals.connect(receiver, signal, function, self._plugin, description, sender)
209
210
    def disconnect(self, receiver):
211
        """
212
        Disconnect a receiver from a signal.
213
        Receiver must exist, otherwise an exception is thrown.
214
215
        :param receiver: Name of the receiver
216
        """
217
        return self.__app.signals.disconnect(receiver)
218
219
    def send(self, signal, **kwargs):
220
        """
221
        Sends a signal for the given plugin.
222
223
        :param signal: Name of the signal
224
        :type signal: str
225
        """
226
        return self.__app.signals.send(signal, plugin=self._plugin, **kwargs)
227
228
    def get(self, signal=None):
229
        """
230
        Returns a single signal or a dictionary of signals for this plugin.
231
        """
232
        return self.__app.signals.get(signal, self._plugin)
233
234
    def get_receiver(self, receiver=None):
235
        """
236
        Returns a single receiver or a dictionary of receivers for this plugin.
237
        """
238
        return self.__app.signals.get_receiver(receiver, self._plugin)
239