Completed
Push — master ( 7723da...556ec7 )
by Daniel
56s
created

GwBasePattern.__init__()   C

Complexity

Conditions 8

Size

Total Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 8
c 2
b 1
f 0
dl 0
loc 51
rs 5.2591

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 sys
10
import os
11
import logging
12
13
from groundwork.patterns.exceptions import PluginAttributeMissing, PluginActivateMissing, PluginDeactivateMissing, \
14
    PluginDependencyLoop
15
16
17
class GwBasePattern(object):
18
    """
19
    Base pattern class for all plugins and patterns.
20
21
    Usage::
22
23
        from groundwork.patterns import GwBasePattern
24
25
        class MyPlugin(GwBasePattern):
26
            def __init__(self, *args, **kwargs):
27
                super().__init__(*args, **kwargs)
28
29
            def activate():
30
                self.signals.register("MySignal", "My description about signal)
31
32
            def deactivate():
33
                self.signals.unregister("MySignal")
34
35
    :param app: groundwork application object, for which the plugin shall be initialised.
36
    :type app: :class:`groundwork.App`.
37
    :param name: Unique name. Normally set by plugin.
38
    """
39
    def __init__(self, app, *args, name=None, **kwargs):
40
        #: groundwork application instance. Access it inside a plugin via ``self.app``.
41
        self.app = app
42
43
        # There must be a name for this plugin. Otherwise it is not detectable and manageable on application level
44
        if not hasattr(self, "name") and name is None:
45
            raise PluginAttributeMissing("Name not set for plugin")
46
        elif not hasattr(self, "name") and name is not None:
47
            # Set the given name, if the plugin itself does not have set one
48
            self.name = name
49
50
        # Be sure we have a set version number. Can be overwritten by the plugin itself.
51
        if not hasattr(self, "version"):
52
            self.version = "0.0.1"
53
54
        # Let's be sure active is false, even if a child class set something different
55
        if not hasattr(self, "active"):
56
            self.active = False
57
58
        # Even if this plugin has no dependencies to other plugins, we should set an empty tuple.
59
        if not hasattr(self, "needed_plugins"):
60
            #: Variable for storing dependencies to other plugins.
61
            #: Tuple must contains needed plugin names.
62
            #: needed_plugins = ("MyPlugin", "MyPlugin2)
63
            self.needed_plugins = ()
64
65
        self.path = sys.modules[self.__module__].__file__
66
        self.file = os.path.basename(self.path)
67
        self.dir = os.path.dirname(self.path)
68
69
        #: A logger, especially created for this plugin. Usage inside a plugin: ``self.log.warn("WARNING!!")``.
70
        #:
71
        #: The logger name is the same as the plugin name. Therefor it is possible to configure the application logging
72
        #: to show log messages of a specif plugin only. See :ref:`plugin_logging`
73
        self.log = logging.getLogger(self.name)
74
75
        #: Instance of :class:`.SignalsPlugin`.
76
        #: Provides functions to register and manage signals and retrievers.
77
        #:
78
        #: All action takes place in the context of this plugin. For instance a ``self.signals.get()`` will return
79
        #: signals of this plugin only. To get all signals of an application, please use ``self.app.signals.get()``.
80
        self.signals = SignalsPlugin(self)
81
82
        # This is used as flag for the pluginManager to be sure, that an initiated class has called the __init__()
83
        # routine of GwPluginPatter
84
        self._plugin_base_initialised = True
85
86
        # Let's tell the pluginManager that this plugin got initialised, so that it gets tracked on app level.
87
        # This is needed if this class gets initiated by hand and the function self.app.plugins._load() was not used
88
        # for doing this job.
89
        self.app.plugins._register_initialisation(self)
90
91
    # def __getattribute__(self, name):
92
    def __getattribute__(self, name):
93
        """
94
        Catches all calls on class attributes, but cares only for activate() and deactivate().
95
96
        If the plugin gets activated or deactivated, the base class can perform extra work.
97
        So there is no need for a plugin developer to call something like super().activate() for
98
        his/her plugin. This gets done automatically.
99
        """
100
101
        attr = object.__getattribute__(self, name)
102
        if hasattr(attr, '__call__'):
103
            if hasattr(attr, "__name__"):
104
                if attr.__name__ == "activate":
105
                    def newfunc(*args, **kwargs):
106
                        self._pre_activate_injection()
107
                        result = attr(*args, **kwargs)
108
                        self._post_activate_injection()
109
                        return result
110
                    return newfunc
111
                elif attr.__name__ == "deactivate":
112
                    def newfunc(*args, **kwargs):
113
                        self._pre_deactivate_injection()
114
                        result = attr(*args, **kwargs)
115
                        self._post_deactivate_injection()
116
                        return result
117
                    return newfunc
118
119
        return attr
120
121
    def activate(self):
122
        """
123
        Must be overwritten by the plugin class itself.
124
        """
125
        self.log.warn("No activation routine in Plugin defined. Define self.activate() in plugin %s" % self.name)
126
        raise PluginActivateMissing("plugin must provide an activation routine by itself.")
127
128
    def deactivate(self):
129
        """
130
        Must be overwritten by the plugin class itself.
131
        """
132
        self.log.warn("No activation routine in Plugin defined. Define self.deactivate() in plugin %s" % self.name)
133
        raise PluginDeactivateMissing("plugin must provide an deactivation routine by itself.")
134
135
    def _pre_activate_injection(self):
136
        """
137
        Injects functions before the activation routine of child classes gets called
138
        """
139
        # Let's be sure that this plugins class is registered and available on application level under
140
        # application.plugins.classes. This allows to reuse this class for *new* plugins.
141
        if not self.app.plugins.classes.exist(self.__class__.__name__):
142
            self.app.plugins.classes.register([self.__class__])
143
144
        self._load_needed_plugins()
145
146
        self.app.signals.send("plugin_activate_pre", self)
147
148
    def _post_activate_injection(self):
149
        """
150
        Injects functions after the activation routine of child classes got called
151
        :return: None
152
        """
153
        self.active = True
154
        self.app.signals.send("plugin_activate_post", self)
155
156
    def _pre_deactivate_injection(self):
157
        """
158
        Injects functions before the deactivation routine of child classes gets called
159
        :return: None
160
        """
161
        self.app.signals.send("plugin_deactivate_pre", self)
162
163
    def _post_deactivate_injection(self):
164
        """
165
        Injects functions after the deactivation routine of child classes got called
166
        :return: None
167
        """
168
        # Lets be sure that active is really set to false.
169
        self.active = False
170
        self.app.signals.send("plugin_deactivate_post", self)
171
        # After all receivers are handled. We start to clean up signals and receivers of this plugin
172
        # Attention: This signal clean must not be called via a signal (like in other patterns),
173
        # because the call order of receivers is not clear and a signal/receiver clean up would prohibit the call
174
        # of all "later" receivers.
175
        self.signals.deactivate_plugin_signals()
176
177
    def _load_needed_plugins(self):
178
        """
179
        Checks if this plugins needs other plugins to work and tries to activate them
180
        :return: True, if all needed plugins are or got activated. Otherwise False
181
        """
182
        global plugin_recursive_store
183
        if "plugin_recursive_store" not in globals():
184
            plugin_recursive_store = []
185
186
        if self.name in plugin_recursive_store:
187
            self.log.warning("Plugin dependency loop detected: %s already checked and dependencies got activated" %
188
                             self.name)
189
            if self.app.strict:
190
                raise PluginDependencyLoop("Plugin dependency loop detected: %s already checked and dependencies "
191
                                           "got activated" % self.name)
192
            return False
193
        else:
194
            plugin_recursive_store.append(self.name)
195
196
        if not hasattr(self, "needed_plugins"):
197
            pass
198
        elif not isinstance(self.needed_plugins, tuple) and not isinstance(self.needed_plugins, list):
199
            raise TypeError("needed_plugins must be a tuple or a list")
200
        elif len(self.needed_plugins) > 0:
201
            try:
202
                for needed_plugin in self.needed_plugins:
203
                    if not isinstance(needed_plugin, str):
204
                        raise TypeError("Plugin name must be a string, got %s" % type(needed_plugin).__name__)
205
                    # Check, if a plugin with this name got already activated
206
                    plugin = self.app.plugins.get(needed_plugin)
207
                    if plugin is not None and not plugin.active:
208
                        plugin.activate()
209
                    # If not, check if a plugin_class with this name is available and activate it
210
                    plugin_class = self.app.plugins.classes.get(needed_plugin)
211
                    if plugin_class is not None:
212
                        plugin_class(self.app, needed_plugin)
213
            except Exception:
214
                plugin_recursive_store.remove(self.name)
215
                return False
216
217
        plugin_recursive_store.remove(self.name)
218
        return True
219
220
221
class SignalsPlugin:
222
    """
223
    Signal and Receiver management class on plugin level.
224
    This class gets initiated once per plugin.
225
226
    Mostly delegates function calls to the :class:`groundwork.signals.SignalListApplication` instance on application
227
    level.
228
229
    :param plugin: The plugin, which wants to use signals
230
    :type plugin: GwBasePattern
231
    """
232
233
    def __init__(self, plugin):
234
        self._plugin = plugin
235
        self.__app = plugin.app
236
        self.__log = plugin.log
237
        self.__log.debug("Plugin messages initialised")
238
239
    def deactivate_plugin_signals(self):
240
        receivers = self.get_receiver()
241
        for receiver in receivers.keys():
242
            self.disconnect(receiver)
243
244
        signals = self.get()
245
        for signal in signals:
246
            self.unregister(signal)
247
248
    def register(self, signal, description):
249
        """
250
        Registers a new signal.
251
        Only registered signals are allowed to be send.
252
253
        :param signal: Unique name of the signal
254
        :param description: Description of the reason or use case, why this signal is needed.
255
                            Used for documentation.
256
        """
257
        return self.__app.signals.register(signal, self._plugin, description)
258
259
    def unregister(self, signal):
260
        return self.__app.signals.unregister(signal)
261
262
    def connect(self, receiver, signal, function, description, sender=None):
263
        """
264
        Connect a receiver to a signal
265
266
        :param receiver: Name of the receiver
267
        :type receiver: str
268
        :param signal: Name of the signal. Must already be registered!
269
        :type signal: str
270
        :param function: Callable functions, which shall be executed, of signal is send.
271
        :param description: Description of the reason or use case, why this connection is needed.
272
                            Used for documentation.
273
        """
274
        return self.__app.signals.connect(receiver, signal, function, self._plugin, description, sender)
275
276
    def disconnect(self, receiver):
277
        """
278
        Disconnect a receiver from a signal.
279
        Receiver must exist, otherwise an exception is thrown.
280
281
        :param receiver: Name of the receiver
282
        """
283
        return self.__app.signals.disconnect(receiver)
284
285
    def send(self, signal, **kwargs):
286
        """
287
        Sends a signal for the given plugin.
288
289
        :param signal: Name of the signal
290
        :type signal: str
291
        """
292
        return self.__app.signals.send(signal, plugin=self._plugin, **kwargs)
293
294
    def get(self, signal=None):
295
        """
296
        Returns a single signal or a dictionary of signals for this plugin.
297
        """
298
        return self.__app.signals.get(signal, self._plugin)
299
300
    def get_receiver(self, receiver=None):
301
        """
302
        Returns a single receiver or a dictionary of receivers for this plugin.
303
        """
304
        return self.__app.signals.get_receiver(receiver, self._plugin)
305