PageRenderTime 91ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/libs/axl/axel.py

https://gitlab.com/132nd-etcher/CouchPotatoServer
Python | 381 lines | 336 code | 4 blank | 41 comment | 1 complexity | 3b38f27994953d43c70806b7f5dcd66a MD5 | raw file
  1. # axel.py
  2. #
  3. # Copyright (C) 2010 Adrian Cristea adrian dot cristea at gmail dotcom
  4. # Edits by Ruud Burger
  5. #
  6. # Based on an idea by Peter Thatcher, found on
  7. # http://www.valuedlessons.com/2008/04/events-in-python.html
  8. #
  9. # This module is part of Axel and is released under
  10. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  11. #
  12. # Source: http://pypi.python.org/pypi/axel
  13. # Docs: http://packages.python.org/axel
  14. from Queue import Empty, Queue
  15. import hashlib
  16. import sys
  17. import threading
  18. from couchpotato.core.helpers.variable import natsortKey
  19. class Event(object):
  20. """
  21. Event object inspired by C# events. Handlers can be registered and
  22. unregistered using += and -= operators. Execution and result are
  23. influenced by the arguments passed to the constructor and += method.
  24. from axel import Event
  25. event = Event()
  26. def on_event(*args, **kwargs):
  27. return (args, kwargs)
  28. event += on_event # handler registration
  29. print(event(10, 20, y=30))
  30. >> ((True, ((10, 20), {'y': 30}), <function on_event at 0x00BAA270>),)
  31. event -= on_event # handler is unregistered
  32. print(event(10, 20, y=30))
  33. >> None
  34. class Mouse(object):
  35. def __init__(self):
  36. self.click = Event(self)
  37. self.click += self.on_click # handler registration
  38. def on_click(self, sender, *args, **kwargs):
  39. assert isinstance(sender, Mouse), 'Wrong sender'
  40. return (args, kwargs)
  41. mouse = Mouse()
  42. print(mouse.click(10, 20))
  43. >> ((True, ((10, 20), {}),
  44. >> <bound method Mouse.on_click of <__main__.Mouse object at 0x00B6F470>>),)
  45. mouse.click -= mouse.on_click # handler is unregistered
  46. print(mouse.click(10, 20))
  47. >> None
  48. """
  49. def __init__(self, name = None, sender = None, asynch = False, exc_info = False,
  50. lock = None, threads = 3, traceback = False):
  51. """ Creates an event
  52. asynch
  53. if True handler's are executes asynchronous
  54. exc_info
  55. if True, result will contain sys.exc_info()[:2] on error
  56. lock
  57. threading.RLock used to synchronize execution
  58. sender
  59. event's sender. The sender is passed as the first argument to the
  60. handler, only if is not None. For this case the handler must have
  61. a placeholder in the arguments to receive the sender
  62. threads
  63. maximum number of threads that will be started
  64. traceback
  65. if True, the execution result will contain sys.exc_info()
  66. on error. exc_info must be also True to get the traceback
  67. hash = self.hash(handler)
  68. Handlers are stored in a dictionary that has as keys the handler's hash
  69. handlers = {
  70. hash : (handler, memoize, timeout),
  71. hash : (handler, memoize, timeout), ...
  72. }
  73. The execution result is cached using the following structure
  74. memoize = {
  75. hash : ((args, kwargs, result), (args, kwargs, result), ...),
  76. hash : ((args, kwargs, result), ...), ...
  77. }
  78. The execution result is returned as a tuple having this structure
  79. exec_result = (
  80. (True, result, handler), # on success
  81. (False, error_info, handler), # on error
  82. (None, None, handler), ... # asynchronous execution
  83. )
  84. """
  85. self.name = name
  86. self.asynchronous = asynch
  87. self.exc_info = exc_info
  88. self.lock = lock
  89. self.sender = sender
  90. self.threads = threads
  91. self.traceback = traceback
  92. self.handlers = {}
  93. self.memoize = {}
  94. def hash(self, handler):
  95. return hashlib.md5(str(handler)).hexdigest()
  96. def handle(self, handler, priority = 0):
  97. """ Registers a handler. The handler can be transmitted together
  98. with two arguments as a list or dictionary. The arguments are:
  99. memoize
  100. if True, the execution result will be cached in self.memoize
  101. timeout
  102. will allocate a predefined time interval for the execution
  103. If arguments are provided as a list, they are considered to have
  104. this sequence: (handler, memoize, timeout)
  105. Examples:
  106. event += handler
  107. event += (handler, True, 1.5)
  108. event += {'handler':handler, 'memoize':True, 'timeout':1.5}
  109. """
  110. handler_, memoize, timeout = self._extract(handler)
  111. self.handlers['%s.%s' % (priority, self.hash(handler_))] = (handler_, memoize, timeout)
  112. return self
  113. def unhandle(self, handler):
  114. """ Unregisters a handler """
  115. handler_, memoize, timeout = self._extract(handler)
  116. key = self.hash(handler_)
  117. if not key in self.handlers:
  118. raise ValueError('Handler "%s" was not found' % str(handler_))
  119. del self.handlers[key]
  120. return self
  121. def fire(self, *args, **kwargs):
  122. """ Stores all registered handlers in a queue for processing """
  123. self.queue = Queue()
  124. result = {}
  125. if self.handlers:
  126. max_threads = 1 if kwargs.get('event_order_lock') else self._threads()
  127. # Set global result
  128. def add_to(key, value):
  129. result[key] = value
  130. kwargs['event_add_to_result'] = add_to
  131. for i in range(max_threads):
  132. t = threading.Thread(target = self._execute,
  133. args = args, kwargs = kwargs)
  134. t.daemon = True
  135. t.start()
  136. handler_keys = self.handlers.keys()
  137. handler_keys.sort(key = natsortKey)
  138. for handler in handler_keys:
  139. self.queue.put(handler)
  140. if self.asynchronous:
  141. handler_, memoize, timeout = self.handlers[handler]
  142. result[handler] = (None, None, handler_)
  143. if not self.asynchronous:
  144. self.queue.join()
  145. return result
  146. def count(self):
  147. """ Returns the count of registered handlers """
  148. return len(self.handlers)
  149. def clear(self):
  150. """ Discards all registered handlers and cached results """
  151. self.handlers.clear()
  152. self.memoize.clear()
  153. def _execute(self, *args, **kwargs):
  154. # Remove get and set from kwargs
  155. add_to_result = kwargs.get('event_add_to_result')
  156. del kwargs['event_add_to_result']
  157. # Get and remove order lock
  158. order_lock = kwargs.get('event_order_lock')
  159. try: del kwargs['event_order_lock']
  160. except: pass
  161. # Get and remove return on first
  162. return_on_result = kwargs.get('event_return_on_result')
  163. try: del kwargs['event_return_on_result']
  164. except: pass
  165. got_results = False
  166. """ Executes all handlers stored in the queue """
  167. while True:
  168. try:
  169. h_ = self.queue.get(timeout = 2)
  170. handler, memoize, timeout = self.handlers[h_]
  171. if return_on_result and got_results:
  172. if not self.asynchronous:
  173. self.queue.task_done()
  174. continue
  175. if order_lock:
  176. order_lock.acquire()
  177. try:
  178. r = self._memoize(memoize, timeout, handler, *args, **kwargs)
  179. if not self.asynchronous:
  180. if not return_on_result or (return_on_result and r[1] is not None):
  181. add_to_result(h_, tuple(r))
  182. got_results = True
  183. except Exception:
  184. if not self.asynchronous:
  185. add_to_result(h_, (False, self._error(sys.exc_info()),
  186. handler))
  187. else:
  188. self.error_handler(sys.exc_info())
  189. finally:
  190. if order_lock:
  191. order_lock.release()
  192. if not self.asynchronous:
  193. self.queue.task_done()
  194. if self.queue.empty():
  195. raise Empty
  196. except Empty:
  197. break
  198. def _extract(self, queue_item):
  199. """ Extracts a handler and handler's arguments that can be provided
  200. as list or dictionary. If arguments are provided as list, they are
  201. considered to have this sequence: (handler, memoize, timeout)
  202. Examples:
  203. event += handler
  204. event += (handler, True, 1.5)
  205. event += {'handler':handler, 'memoize':True, 'timeout':1.5}
  206. """
  207. assert queue_item, 'Invalid list of arguments'
  208. handler = None
  209. memoize = False
  210. timeout = 0
  211. if not isinstance(queue_item, (list, tuple, dict)):
  212. handler = queue_item
  213. elif isinstance(queue_item, (list, tuple)):
  214. if len(queue_item) == 3:
  215. handler, memoize, timeout = queue_item
  216. elif len(queue_item) == 2:
  217. handler, memoize, = queue_item
  218. elif len(queue_item) == 1:
  219. handler = queue_item
  220. elif isinstance(queue_item, dict):
  221. handler = queue_item.get('handler')
  222. memoize = queue_item.get('memoize', False)
  223. timeout = queue_item.get('timeout', 0)
  224. return (handler, bool(memoize), float(timeout))
  225. def _memoize(self, memoize, timeout, handler, *args, **kwargs):
  226. """ Caches the execution result of successful executions
  227. hash = self.hash(handler)
  228. memoize = {
  229. hash : ((args, kwargs, result), (args, kwargs, result), ...),
  230. hash : ((args, kwargs, result), ...), ...
  231. }
  232. """
  233. if not isinstance(handler, Event) and self.sender is not None:
  234. args = list(args)[:]
  235. args.insert(0, self.sender)
  236. if not memoize:
  237. if timeout <= 0: #no time restriction
  238. result = [True, handler(*args, **kwargs), handler]
  239. return result
  240. result = self._timeout(timeout, handler, *args, **kwargs)
  241. if isinstance(result, tuple) and len(result) == 3:
  242. if isinstance(result[1], Exception): #error occurred
  243. return [False, self._error(result), handler]
  244. return [True, result, handler]
  245. else:
  246. hash_ = self.hash(handler)
  247. if hash_ in self.memoize:
  248. for args_, kwargs_, result in self.memoize[hash_]:
  249. if args_ == args and kwargs_ == kwargs:
  250. return [True, result, handler]
  251. if timeout <= 0: #no time restriction
  252. result = handler(*args, **kwargs)
  253. else:
  254. result = self._timeout(timeout, handler, *args, **kwargs)
  255. if isinstance(result, tuple) and len(result) == 3:
  256. if isinstance(result[1], Exception): #error occurred
  257. return [False, self._error(result), handler]
  258. lock = threading.RLock()
  259. lock.acquire()
  260. try:
  261. if hash_ not in self.memoize:
  262. self.memoize[hash_] = []
  263. self.memoize[hash_].append((args, kwargs, result))
  264. return [True, result, handler]
  265. finally:
  266. lock.release()
  267. def _timeout(self, timeout, handler, *args, **kwargs):
  268. """ Controls the time allocated for the execution of a method """
  269. t = spawn_thread(target = handler, args = args, kwargs = kwargs)
  270. t.daemon = True
  271. t.start()
  272. t.join(timeout)
  273. if not t.is_alive():
  274. if t.exc_info:
  275. return t.exc_info
  276. return t.result
  277. else:
  278. try:
  279. msg = '[%s] Execution was forcefully terminated'
  280. raise RuntimeError(msg % t.name)
  281. except:
  282. return sys.exc_info()
  283. def _threads(self):
  284. """ Calculates maximum number of threads that will be started """
  285. if self.threads < len(self.handlers):
  286. return self.threads
  287. return len(self.handlers)
  288. def _error(self, exc_info):
  289. """ Retrieves the error info """
  290. if self.exc_info:
  291. if self.traceback:
  292. return exc_info
  293. return exc_info[:2]
  294. return exc_info[1]
  295. __iadd__ = handle
  296. __isub__ = unhandle
  297. __call__ = fire
  298. __len__ = count
  299. class spawn_thread(threading.Thread):
  300. """ Spawns a new thread and returns the execution result """
  301. def __init__(self, target, args = (), kwargs = {}, default = None):
  302. threading.Thread.__init__(self)
  303. self._target = target
  304. self._args = args
  305. self._kwargs = kwargs
  306. self.result = default
  307. self.exc_info = None
  308. def run(self):
  309. try:
  310. self.result = self._target(*self._args, **self._kwargs)
  311. except:
  312. self.exc_info = sys.exc_info()
  313. finally:
  314. del self._target, self._args, self._kwargs