Completed
Push — master ( e004f0...1a0efd )
by Daniel
01:07
created

ThreadWrapper   A

Complexity

Total Complexity 2

Size/Duplication

Total Lines 18
Duplicated Lines 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 18
rs 10
wmc 2

2 Methods

Rating   Name   Duplication   Size   Complexity  
A __init__() 0 5 1
A run() 0 6 1
1
"""
2
Groundwork threads support module.
3
"""
4
5
import logging
6
import threading
7
import datetime
8
9
from groundwork.patterns.gw_base_pattern import GwBasePattern
10
11
12
class GwThreadsPattern(GwBasePattern):
13
    """
14
    Threads can be created and started to perform tasks in the background and in parallel to the main application.
15
16
    Please see :ref:`threads` for more details.
17
    """
18
19
    def __init__(self, *args, **kwargs):
20
        super().__init__(*args, **kwargs)
21
22
        if not hasattr(self.app, "threads"):
23
            self.app.threads = ThreadsListApplication(self.app)
24
25
        #: Stores an instance of :class:`~groundwork.patterns.gw_threads_pattern.ThreadsListPlugin`
26
        self.threads = ThreadsListPlugin(self)
27
28
29 View Code Duplication
class ThreadsListPlugin:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
30
    """
31
    Stores and handles threads.
32
33
    Please see :ref:`threads` for more details.
34
    """
35
36
    def __init__(self, plugin):
37
        """
38
        :param plugin: The plugin, which wants to use threads
39
        :type plugin: GwBasePattern
40
        """
41
        self._plugin = plugin
42
        self.__app = plugin.app
43
        self.__log = plugin.log
44
45
        # Let's register a receiver, which cares about the deactivation process of threads for this plugin.
46
        # We do it after the original plugin deactivation, so we can be sure that the registered function is the last
47
        # one which cares about threads for this plugin.
48
        self._plugin.signals.connect(receiver="%s_threads_deactivation" % self._plugin.name,
49
                                     signal="plugin_deactivate_post",
50
                                     function=self.__deactivate_threads,
51
                                     description="Deactivate threads for %s" % self._plugin.name,
52
                                     sender=self._plugin)
53
        self.__log.debug("Plugin threads initialised")
54
55
    def __deactivate_threads(self, plugin, *args, **kwargs):
56
        threads = self.get()
57
        for thread in threads.keys():
58
            self.unregister(thread)
59
60
    def register(self, name, function, description=None):
61
        """
62
        Register a new thread.
63
64
        :param function: Function, which gets called for the new thread
65
        :type function: function
66
        :param name: Unique name of the thread for documentation purposes.
67
        :param description: Short description of the thread
68
        """
69
        return self.__app.threads.register(name, function, self._plugin, description)
70
71
    def unregister(self, thread):
72
        return self.__app.threads.unregister(thread)
73
74
    def get(self, name=None):
75
        return self.__app.threads.get(name, self._plugin)
76
77
78
class ThreadsListApplication:
79
    """
80
81
    """
82
83
    def __init__(self, app):
84
        self.__app = app
85
        self.__log = logging.getLogger(__name__)
86
        self.threads = {}
87
        self.__log.info("Application threads initialised")
88
89
    def register(self, name, function, plugin, description=None):
90
        """
91
        Registers a new document.
92
93
        .. warning: You can not use any relative links inside a given document.
94
                    For instance, sphinx's toctree, image, figure or include statements do not work.
95
96
        :param function: Function, which gets called for the new thread
97
        :type function: function
98
        :param name: Unique name of the thread for documentation purposes.
99
        :param plugin: Plugin object, under which the threads where registered
100
        :type plugin: GwBasePattern
101
        :param description: Short description of the thread
102
        """
103
        if name in self.threads.keys():
104
            raise ThreadExistsException("Thread %s was already registered by %s" %
105
                                        (name, self.threads[name].plugin.name))
106
107
        self.threads[name] = Thread(name, function, plugin, description)
108
        self.__log.debug("Thread %s registered by %s" % (name, plugin.name))
109
        return self.threads[name]
110
111
    def unregister(self, thread):
112
        """
113
        Unregisters an existing thread, so that this thread is no longer available.
114
115
        This function is mainly used during plugin deactivation.
116
117
        :param thread: Name of the thread
118
        """
119
        if thread not in self.threads.keys():
120
            self.log.warning("Can not unregister thread %s" % thread)
121
        else:
122
            del (self.threads[thread])
123
            self.__log.debug("Thread %s got unregistered" % thread)
124
125
    def get(self, thread=None, plugin=None):
126
        """
127
        Get one or more threads.
128
129
        :param thread: Name of the thread
130
        :type thread: str
131
        :param plugin: Plugin object, under which the thread was registered
132
        :type plugin: GwBasePattern
133
        """
134
        if plugin is not None:
135
            if thread is None:
136
                threads_list = {}
137
                for key in self.threads.keys():
138
                    if self.threads[key].plugin == plugin:
139
                        threads_list[key] = self.threads[key]
140
                return threads_list
141
            else:
142
                if thread in self.threads.keys():
143
                    if self.threads[thread].plugin == plugin:
144
                        return self.threads[thread]
145
                    else:
146
                        return None
147
                else:
148
                    return None
149
        else:
150
            if thread is None:
151
                return self.threads
152
            else:
153
                if thread in self.threads.keys():
154
                    return self.threads[thread]
155
                else:
156
                    return None
157
158
159
class Thread:
160
    """
161
    Groundwork thread class. Used to store name, function and plugin.
162
163
    This information is mostly used to generated overviews about registered threads.
164
165
    :param name: Name of the thread
166
    :type name: str
167
    :param function: Function, which gets called inside the thread
168
    :type function: function
169
    :param plugin: The plugin, which registered this thread
170
    :type plugin: GwBasePattern
171
    :param description: short description of this thread
172
    """
173
    def __init__(self, name, function, plugin, description=None):
174
        self.name = name
175
        self.function = function
176
        self.plugin = plugin
177
        self.description = description
178
179
        #: Thread base class. Type is threading.Thread
180
        self.thread = ThreadWrapper(self)
181
182
        #: Stores the function return value, if thread has finished
183
        self.response = None
184
185
        #: datetime object of the starting moment
186
        self.time_start = None
187
188
        #: datetime object of the ending moment
189
        self.time_end = None
190
191
        #: True, if thread is running. Otherwise its False.
192
        self.running = False
193
194
    def run(self, **kwargs):
195
        """
196
        Runs the thread
197
198
        :param kwargs: dictionary of keyword arguments
199
        :return:
200
        """
201
        self.thread.run(**kwargs)
202
203
204
class ThreadWrapper(threading.Thread):
205
    """
206
    Wrapper class, which inherits from threading.Thread and performs some useful tasks
207
    before and after the provided functions gets executed.
208
209
    """
210
    def __init__(self, thread):
211
        super().__init__()
212
        self.thread = thread
213
        self.plugin = thread.plugin
214
        self.app = thread.plugin.app
215
216
    def run(self, **kwargs):
217
        self.thread.running = True
218
        self.thread.time_start = datetime.datetime.now()
219
        self.thread.response = self.thread.function(self.plugin, **kwargs)
220
        self.thread.time_end = datetime.datetime.now()
221
        self.thread.running = False
222
223
224
class ThreadExistsException(BaseException):
225
    pass
226