Completed
Push — master ( abd813...08e22e )
by Daniel
03:40
created

GwBasePattern.__getattribute__()   C

Complexity

Conditions 7

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 7
c 3
b 1
f 0
dl 0
loc 28
rs 5.5

1 Method

Rating   Name   Duplication   Size   Complexity  
A GwBasePattern.newfunc() 0 5 1
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 hasattr(attr, "__name__"):
82
                if attr.__name__ == "activate":
83
                    def newfunc(*args, **kwargs):
84
                        self._pre_activate_injection()
85
                        result = attr(*args, **kwargs)
86
                        self._post_activate_injection()
87
                        return result
88
                    return newfunc
89
                elif attr.__name__ == "deactivate":
90
                    def newfunc(*args, **kwargs):
91
                        self._pre_deactivate_injection()
92
                        result = attr(*args, **kwargs)
93
                        self._post_deactivate_injection()
94
                        return result
95
                    return newfunc
96
97
        return attr
98
99
    def activate(self):
100
        """
101
        Must be overwritten by the plugin class itself.
102
        """
103
        self.log.warn("No activation routine in Plugin defined. Define self.activate() in plugin %s" % self.name)
104
        raise PluginActivateMissing("plugin must provide an activation routine by itself.")
105
106
    def deactivate(self):
107
        """
108
        Must be overwritten by the plugin class itself.
109
        """
110
        self.log.warn("No activation routine in Plugin defined. Define self.deactivate() in plugin %s" % self.name)
111
        raise PluginDeactivateMissing("plugin must provide an deactivation routine by itself.")
112
113
    def _pre_activate_injection(self):
114
        """
115
        Injects functions before the activation routine of child classes gets called
116
        """
117
        # Let's be sure that this plugins class is registered and available on application level under
118
        # application.plugins.classes. This allows to reuse this class for *new* plugins.
119
        if not self.app.plugins.classes.exist(self.__class__.__name__):
120
            self.app.plugins.classes.register([self.__class__])
121
122
        self.app.signals.send("plugin_activate_pre", self)
123
124
    def _post_activate_injection(self):
125
        """
126
        Injects functions after the activation routine of child classes got called
127
        :return: None
128
        """
129
        self.active = True
130
        self.app.signals.send("plugin_activate_post", self)
131
132
    def _pre_deactivate_injection(self):
133
        """
134
        Injects functions before the deactivation routine of child classes gets called
135
        :return: None
136
        """
137
        self.app.signals.send("plugin_deactivate_pre", self)
138
139
    def _post_deactivate_injection(self):
140
        """
141
        Injects functions after the deactivation routine of child classes got called
142
        :return: None
143
        """
144
        # Lets be sure that active is really set to false.
145
        self.active = False
146
        self.app.signals.send("plugin_deactivate_post", self)
147
        # After all receivers are handled. We start to clean up signals and receivers of this plugin
148
        # Attention: This signal clean must not be called via a signal (like in other patterns),
149
        # because the call order of receivers is not clear and a signal/receiver clean up would prohibit the call
150
        # of all "later" receivers.
151
        self.signals.deactivate_plugin_signals()
152
153
154
class SignalsPlugin:
155
    """
156
    Signal and Receiver management class on plugin level.
157
    This class gets initiated once per plugin.
158
159
    Mostly delegates function calls to the :class:`groundwork.signals.SignalListApplication` instance on application
160
    level.
161
162
    :param plugin: The plugin, which wants to use signals
163
    :type plugin: GwBasePattern
164
    """
165
166
    def __init__(self, plugin):
167
        self._plugin = plugin
168
        self.__app = plugin.app
169
        self.__log = plugin.log
170
        self.__log.info("Plugin messages initialised")
171
172
    def deactivate_plugin_signals(self):
173
        receivers = self.get_receiver()
174
        for receiver in receivers.keys():
175
            self.disconnect(receiver)
176
177
        signals = self.get()
178
        for signal in signals:
179
            self.unregister(signal)
180
181
    def register(self, signal, description):
182
        """
183
        Registers a new signal.
184
        Only registered signals are allowed to be send.
185
186
        :param signal: Unique name of the signal
187
        :param description: Description of the reason or use case, why this signal is needed.
188
                            Used for documentation.
189
        """
190
        return self.__app.signals.register(signal, self._plugin, description)
191
192
    def unregister(self, signal):
193
        return self.__app.signals.unregister(signal)
194
195
    def connect(self, receiver, signal, function, description, sender=None):
196
        """
197
        Connect a receiver to a signal
198
199
        :param receiver: Name of the receiver
200
        :type receiver: str
201
        :param signal: Name of the signal. Must already be registered!
202
        :type signal: str
203
        :param function: Callable functions, which shall be executed, of signal is send.
204
        :param description: Description of the reason or use case, why this connection is needed.
205
                            Used for documentation.
206
        """
207
        return self.__app.signals.connect(receiver, signal, function, self._plugin, description, sender)
208
209
    def disconnect(self, receiver):
210
        """
211
        Disconnect a receiver from a signal.
212
        Receiver must exist, otherwise an exception is thrown.
213
214
        :param receiver: Name of the receiver
215
        """
216
        return self.__app.signals.disconnect(receiver)
217
218
    def send(self, signal, **kwargs):
219
        """
220
        Sends a signal for the given plugin.
221
222
        :param signal: Name of the signal
223
        :type signal: str
224
        """
225
        return self.__app.signals.send(signal, plugin=self._plugin, **kwargs)
226
227
    def get(self, signal=None):
228
        """
229
        Returns a single signal or a dictionary of signals for this plugin.
230
        """
231
        return self.__app.signals.get(signal, self._plugin)
232
233
    def get_receiver(self, receiver=None):
234
        """
235
        Returns a single receiver or a dictionary of receivers for this plugin.
236
        """
237
        return self.__app.signals.get_receiver(receiver, self._plugin)
238