You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

487 lines
16 KiB

4 years ago
  1. """Multiple-producer-multiple-consumer signal-dispatching
  2. dispatcher is the core of the PyDispatcher system,
  3. providing the primary API and the core logic for the
  4. system.
  5. Module attributes of note:
  6. Any -- Singleton used to signal either "Any Sender" or
  7. "Any Signal". See documentation of the _Any class.
  8. Anonymous -- Singleton used to signal "Anonymous Sender"
  9. See documentation of the _Anonymous class.
  10. Internal attributes:
  11. WEAKREF_TYPES -- tuple of types/classes which represent
  12. weak references to receivers, and thus must be de-
  13. referenced on retrieval to retrieve the callable
  14. object
  15. connections -- { senderkey (id) : { signal : [receivers...]}}
  16. senders -- { senderkey (id) : weakref(sender) }
  17. used for cleaning up sender references on sender
  18. deletion
  19. sendersBack -- { receiverkey (id) : [senderkey (id)...] }
  20. used for cleaning up receiver references on receiver
  21. deletion, (considerably speeds up the cleanup process
  22. vs. the original code.)
  23. """
  24. import weakref
  25. from pydispatch import saferef, robustapply, errors
  26. class _Parameter:
  27. """Used to represent default parameter values."""
  28. def __repr__(self):
  29. return self.__class__.__name__
  30. class _Any(_Parameter):
  31. """Singleton used to signal either "Any Sender" or "Any Signal"
  32. The Any object can be used with connect, disconnect,
  33. send, or sendExact to signal that the parameter given
  34. Any should react to all senders/signals, not just
  35. a particular sender/signal.
  36. """
  37. Any = _Any()
  38. class _Anonymous(_Parameter):
  39. """Singleton used to signal "Anonymous Sender"
  40. The Anonymous object is used to signal that the sender
  41. of a message is not specified (as distinct from being
  42. "any sender"). Registering callbacks for Anonymous
  43. will only receive messages sent without senders. Sending
  44. with anonymous will only send messages to those receivers
  45. registered for Any or Anonymous.
  46. Note:
  47. The default sender for connect is Any, while the
  48. default sender for send is Anonymous. This has
  49. the effect that if you do not specify any senders
  50. in either function then all messages are routed
  51. as though there was a single sender (Anonymous)
  52. being used everywhere.
  53. """
  54. Anonymous = _Anonymous()
  55. WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
  56. connections = {}
  57. senders = {}
  58. sendersBack = {}
  59. def connect(receiver, signal=Any, sender=Any, weak=True):
  60. """Connect receiver to sender for signal
  61. receiver -- a callable Python object which is to receive
  62. messages/signals/events. Receivers must be hashable
  63. objects.
  64. if weak is True, then receiver must be weak-referencable
  65. (more precisely saferef.safeRef() must be able to create
  66. a reference to the receiver).
  67. Receivers are fairly flexible in their specification,
  68. as the machinery in the robustApply module takes care
  69. of most of the details regarding figuring out appropriate
  70. subsets of the sent arguments to apply to a given
  71. receiver.
  72. Note:
  73. if receiver is itself a weak reference (a callable),
  74. it will be de-referenced by the system's machinery,
  75. so *generally* weak references are not suitable as
  76. receivers, though some use might be found for the
  77. facility whereby a higher-level library passes in
  78. pre-weakrefed receiver references.
  79. signal -- the signal to which the receiver should respond
  80. if Any, receiver will receive any signal from the
  81. indicated sender (which might also be Any, but is not
  82. necessarily Any).
  83. Otherwise must be a hashable Python object other than
  84. None (DispatcherError raised on None).
  85. sender -- the sender to which the receiver should respond
  86. if Any, receiver will receive the indicated signals
  87. from any sender.
  88. if Anonymous, receiver will only receive indicated
  89. signals from send/sendExact which do not specify a
  90. sender, or specify Anonymous explicitly as the sender.
  91. Otherwise can be any python object.
  92. weak -- whether to use weak references to the receiver
  93. By default, the module will attempt to use weak
  94. references to the receiver objects. If this parameter
  95. is false, then strong references will be used.
  96. returns None, may raise DispatcherTypeError
  97. """
  98. if signal is None:
  99. raise errors.DispatcherTypeError(
  100. 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
  101. )
  102. if weak:
  103. receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
  104. senderkey = id(sender)
  105. if senderkey in connections:
  106. signals = connections[senderkey]
  107. else:
  108. connections[senderkey] = signals = {}
  109. # Keep track of senders for cleanup.
  110. # Is Anonymous something we want to clean up?
  111. if sender not in (None, Anonymous, Any):
  112. def remove(object, senderkey=senderkey):
  113. _removeSender(senderkey=senderkey)
  114. # Skip objects that can not be weakly referenced, which means
  115. # they won't be automatically cleaned up, but that's too bad.
  116. try:
  117. weakSender = weakref.ref(sender, remove)
  118. senders[senderkey] = weakSender
  119. except:
  120. pass
  121. receiverID = id(receiver)
  122. # get current set, remove any current references to
  123. # this receiver in the set, including back-references
  124. if signal in signals:
  125. receivers = signals[signal]
  126. _removeOldBackRefs(senderkey, signal, receiver, receivers)
  127. else:
  128. receivers = signals[signal] = []
  129. try:
  130. current = sendersBack.get( receiverID )
  131. if current is None:
  132. sendersBack[ receiverID ] = current = []
  133. if senderkey not in current:
  134. current.append(senderkey)
  135. except:
  136. pass
  137. receivers.append(receiver)
  138. def disconnect(receiver, signal=Any, sender=Any, weak=True):
  139. """Disconnect receiver from sender for signal
  140. receiver -- the registered receiver to disconnect
  141. signal -- the registered signal to disconnect
  142. sender -- the registered sender to disconnect
  143. weak -- the weakref state to disconnect
  144. disconnect reverses the process of connect,
  145. the semantics for the individual elements are
  146. logically equivalent to a tuple of
  147. (receiver, signal, sender, weak) used as a key
  148. to be deleted from the internal routing tables.
  149. (The actual process is slightly more complex
  150. but the semantics are basically the same).
  151. Note:
  152. Using disconnect is not required to cleanup
  153. routing when an object is deleted, the framework
  154. will remove routes for deleted objects
  155. automatically. It's only necessary to disconnect
  156. if you want to stop routing to a live object.
  157. returns None, may raise DispatcherTypeError or
  158. DispatcherKeyError
  159. """
  160. if signal is None:
  161. raise errors.DispatcherTypeError(
  162. 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
  163. )
  164. if weak: receiver = saferef.safeRef(receiver)
  165. senderkey = id(sender)
  166. try:
  167. signals = connections[senderkey]
  168. receivers = signals[signal]
  169. except KeyError:
  170. raise errors.DispatcherKeyError(
  171. """No receivers found for signal %r from sender %r""" %(
  172. signal,
  173. sender
  174. )
  175. )
  176. try:
  177. # also removes from receivers
  178. _removeOldBackRefs(senderkey, signal, receiver, receivers)
  179. except ValueError:
  180. raise errors.DispatcherKeyError(
  181. """No connection to receiver %s for signal %s from sender %s""" %(
  182. receiver,
  183. signal,
  184. sender
  185. )
  186. )
  187. _cleanupConnections(senderkey, signal)
  188. def getReceivers( sender = Any, signal = Any ):
  189. """Get list of receivers from global tables
  190. This utility function allows you to retrieve the
  191. raw list of receivers from the connections table
  192. for the given sender and signal pair.
  193. Note:
  194. there is no guarantee that this is the actual list
  195. stored in the connections table, so the value
  196. should be treated as a simple iterable/truth value
  197. rather than, for instance a list to which you
  198. might append new records.
  199. Normally you would use liveReceivers( getReceivers( ...))
  200. to retrieve the actual receiver objects as an iterable
  201. object.
  202. """
  203. try:
  204. return connections[id(sender)][signal]
  205. except KeyError:
  206. return []
  207. def liveReceivers(receivers):
  208. """Filter sequence of receivers to get resolved, live receivers
  209. This is a generator which will iterate over
  210. the passed sequence, checking for weak references
  211. and resolving them, then returning all live
  212. receivers.
  213. """
  214. for receiver in receivers:
  215. if isinstance( receiver, WEAKREF_TYPES):
  216. # Dereference the weak reference.
  217. receiver = receiver()
  218. if receiver is not None:
  219. yield receiver
  220. else:
  221. yield receiver
  222. def getAllReceivers( sender = Any, signal = Any ):
  223. """Get list of all receivers from global tables
  224. This gets all receivers which should receive
  225. the given signal from sender, each receiver should
  226. be produced only once by the resulting generator
  227. """
  228. receivers = {}
  229. for set in (
  230. # Get receivers that receive *this* signal from *this* sender.
  231. getReceivers( sender, signal ),
  232. # Add receivers that receive *any* signal from *this* sender.
  233. getReceivers( sender, Any ),
  234. # Add receivers that receive *this* signal from *any* sender.
  235. getReceivers( Any, signal ),
  236. # Add receivers that receive *any* signal from *any* sender.
  237. getReceivers( Any, Any ),
  238. ):
  239. for receiver in set:
  240. if receiver: # filter out dead instance-method weakrefs
  241. try:
  242. if receiver not in receivers:
  243. receivers[receiver] = 1
  244. yield receiver
  245. except TypeError:
  246. # dead weakrefs raise TypeError on hash...
  247. pass
  248. def send(signal=Any, sender=Anonymous, *arguments, **named):
  249. """Send signal from sender to all connected receivers.
  250. signal -- (hashable) signal value, see connect for details
  251. sender -- the sender of the signal
  252. if Any, only receivers registered for Any will receive
  253. the message.
  254. if Anonymous, only receivers registered to receive
  255. messages from Anonymous or Any will receive the message
  256. Otherwise can be any python object (normally one
  257. registered with a connect if you actually want
  258. something to occur).
  259. arguments -- positional arguments which will be passed to
  260. *all* receivers. Note that this may raise TypeErrors
  261. if the receivers do not allow the particular arguments.
  262. Note also that arguments are applied before named
  263. arguments, so they should be used with care.
  264. named -- named arguments which will be filtered according
  265. to the parameters of the receivers to only provide those
  266. acceptable to the receiver.
  267. Return a list of tuple pairs [(receiver, response), ... ]
  268. if any receiver raises an error, the error propagates back
  269. through send, terminating the dispatch loop, so it is quite
  270. possible to not have all receivers called if a raises an
  271. error.
  272. """
  273. # Call each receiver with whatever arguments it can accept.
  274. # Return a list of tuple pairs [(receiver, response), ... ].
  275. responses = []
  276. for receiver in liveReceivers(getAllReceivers(sender, signal)):
  277. response = robustapply.robustApply(
  278. receiver,
  279. signal=signal,
  280. sender=sender,
  281. *arguments,
  282. **named
  283. )
  284. responses.append((receiver, response))
  285. return responses
  286. def sendExact( signal=Any, sender=Anonymous, *arguments, **named ):
  287. """Send signal only to those receivers registered for exact message
  288. sendExact allows for avoiding Any/Anonymous registered
  289. handlers, sending only to those receivers explicitly
  290. registered for a particular signal on a particular
  291. sender.
  292. """
  293. responses = []
  294. for receiver in liveReceivers(getReceivers(sender, signal)):
  295. response = robustapply.robustApply(
  296. receiver,
  297. signal=signal,
  298. sender=sender,
  299. *arguments,
  300. **named
  301. )
  302. responses.append((receiver, response))
  303. return responses
  304. def _removeReceiver(receiver):
  305. """Remove receiver from connections."""
  306. if not sendersBack:
  307. # During module cleanup the mapping will be replaced with None
  308. return False
  309. backKey = id(receiver)
  310. try:
  311. backSet = sendersBack.pop(backKey)
  312. except KeyError:
  313. return False
  314. else:
  315. for senderkey in backSet:
  316. try:
  317. signals = list(connections[senderkey].keys())
  318. except KeyError:
  319. pass
  320. else:
  321. for signal in signals:
  322. try:
  323. receivers = connections[senderkey][signal]
  324. except KeyError:
  325. pass
  326. else:
  327. try:
  328. receivers.remove( receiver )
  329. except Exception:
  330. pass
  331. _cleanupConnections(senderkey, signal)
  332. def _cleanupConnections(senderkey, signal):
  333. """Delete any empty signals for senderkey. Delete senderkey if empty."""
  334. try:
  335. receivers = connections[senderkey][signal]
  336. except:
  337. pass
  338. else:
  339. if not receivers:
  340. # No more connected receivers. Therefore, remove the signal.
  341. try:
  342. signals = connections[senderkey]
  343. except KeyError:
  344. pass
  345. else:
  346. del signals[signal]
  347. if not signals:
  348. # No more signal connections. Therefore, remove the sender.
  349. _removeSender(senderkey)
  350. def _removeSender(senderkey):
  351. """Remove senderkey from connections."""
  352. _removeBackrefs(senderkey)
  353. try:
  354. del connections[senderkey]
  355. except KeyError:
  356. pass
  357. # Senderkey will only be in senders dictionary if sender
  358. # could be weakly referenced.
  359. try:
  360. del senders[senderkey]
  361. except:
  362. pass
  363. def _removeBackrefs( senderkey):
  364. """Remove all back-references to this senderkey"""
  365. try:
  366. signals = connections[senderkey]
  367. except KeyError:
  368. signals = None
  369. else:
  370. items = signals.items()
  371. def allReceivers( ):
  372. for signal,set in items:
  373. for item in set:
  374. yield item
  375. for receiver in allReceivers():
  376. _killBackref( receiver, senderkey )
  377. def _removeOldBackRefs(senderkey, signal, receiver, receivers):
  378. """Kill old sendersBack references from receiver
  379. This guards against multiple registration of the same
  380. receiver for a given signal and sender leaking memory
  381. as old back reference records build up.
  382. Also removes old receiver instance from receivers
  383. """
  384. try:
  385. index = receivers.index(receiver)
  386. # need to scan back references here and remove senderkey
  387. except ValueError:
  388. return False
  389. else:
  390. oldReceiver = receivers[index]
  391. del receivers[index]
  392. found = 0
  393. signals = connections.get(signal)
  394. if signals is not None:
  395. for sig,recs in connections.get(signal,{}).items():
  396. if sig != signal:
  397. for rec in recs:
  398. if rec is oldReceiver:
  399. found = 1
  400. break
  401. if not found:
  402. _killBackref( oldReceiver, senderkey )
  403. return True
  404. return False
  405. def _killBackref( receiver, senderkey ):
  406. """Do the actual removal of back reference from receiver to senderkey"""
  407. receiverkey = id(receiver)
  408. set = sendersBack.get( receiverkey, () )
  409. while senderkey in set:
  410. try:
  411. set.remove( senderkey )
  412. except:
  413. break
  414. if not set:
  415. try:
  416. del sendersBack[ receiverkey ]
  417. except KeyError:
  418. pass
  419. return True