{ "info": { "author": "Deterministic Arts", "author_email": "dirk.esser@deterministic-arts.de", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.0", "Programming Language :: Python :: 3.1", "Topic :: Utilities" ], "description": "Preface\r\n========\r\n\r\nIntroduction\r\n-------------\r\n\r\nThis library provides a simple event dispatcher, similar to the\r\n`event` construct provided by the C# language. The library has no\r\nexternal dependencies.\r\n\r\nIt was intended for use cases, where the components emitting events \r\nand the components listening for events agree about the type of \r\nevent and the semantics associated with it. This is true, for example,\r\nfor event handlers, which listen on \"click\" events signalled by\r\nGUI button objects, or notifications signalled by objects, whenever\r\nthe value of some property changes. This differs from the approach\r\ntaken by, say, the `PyDispatcher`, which is more generic, and \r\nfavours communication among weakly coupled components.\r\n\r\n\r\nCompatibility\r\n--------------\r\n\r\nThe code was mainly written for and tested with Python 2.6. It is known\r\nto work with Python 3.2. It should be compatible to 2.5 as well, \r\nbut you might have to insert a few `from __future__ import with_statement` \r\nlines here and there (and this is generally untested). It should work \r\n(this has not been tested) with alternative implementations of Python \r\nlike Jython or IronPython. Note, though, that some of the test cases \r\ndefined in this file might fail due to different garbage collection \r\nimplementations; this file was written with CPython in mind.\r\n\r\n\r\nDocumentation\r\n==============\r\n\r\nBasic Usage\r\n------------\r\n\r\n >>> from darts.lib.utils.event import Publisher, ReferenceRetention as RR\r\n >>> some_event = Publisher()\r\n\r\nThe `Publisher` is the main component. It acts as registry for \r\ncallbacks/listeners. Let's define a listener\r\n\r\n >>> def printer(*event_args, **event_keys):\r\n ... print event_args, event_keys\r\n\r\nIn order to receive notifications, clients must subscribe to a \r\npublisher. This can be as simple as\r\n\r\n >>> some_event.subscribe(printer) #doctest: +ELLIPSIS\r\n \r\n\r\nThe result of the call to `subscribe` is an instance of (some subclass\r\nof) class `Subscription`. This value may be used later, in order to \r\ncancel the subscription, when notifications are no longer desired. The\r\nactual subclass is an implementation detail you should normally not\r\ncare about. All you need to know (and are allowed to rely on, in fact)\r\nis, that it will be an instance of class `Subscription`, and it will\r\nprovide whatever has been documented as public API of that class (right\r\nnow: only method `cancel`).\r\n\r\nNow, let's signal an event and see what happens:\r\n\r\n >>> some_event.publish('an-event')\r\n ('an-event',) {}\r\n\r\nAs you can see, the `printer` has been notified of the event, and\r\nduefully printed the its arguments to the console.\r\n\r\n\r\nCancelling subscriptions\r\n-------------------------\r\n\r\nAs mentioned, the result of calling `subscribe` is a special subscription\r\nobject, which represents the registration of the listener with the \r\npublisher.\r\n\r\n >>> s1 = some_event.subscribe(printer)\r\n >>> some_event.publish('another-event')\r\n ('another-event',) {}\r\n ('another-event',) {}\r\n >>> s1.cancel()\r\n True\r\n >>> some_event.publish('yet-another-one')\r\n ('yet-another-one',) {}\r\n\r\nThe publisher is fully re-entrant. That means, that you can subscribe\r\nto events from within a listener, and you can cancel subscriptions in\r\nthat context as well:\r\n\r\n >>> def make_canceller(subs):\r\n ... def listener(*unused_1, **unused_2):\r\n ... print \"Cancel\", subs, subs.cancel()\r\n ... return listener\r\n >>> s1 = some_event.subscribe(printer)\r\n >>> s2 = some_event.subscribe(make_canceller(s1))\r\n >>> some_event.publish('gotta-go') #doctest: +ELLIPSIS\r\n ('gotta-go',) {}\r\n ('gotta-go',) {}\r\n Cancel True\r\n >>> some_event.publish('gone') #doctest: +ELLIPSIS\r\n ('gone',) {}\r\n Cancel False\r\n >>> s1.cancel()\r\n False\r\n\r\nThe result of the call to `cancel` tells us, that the subscription had \r\nalready been undone prior to the call (by our magic cancellation listener). \r\nGenerally, calling `cancel` multiple times is harmless; all but the first \r\ncall are ignored.\r\n\r\nLet's now remove the magic I-can-cancel-stuff listener and move on:\r\n\r\n >>> s2.cancel()\r\n True\r\n\r\n\r\nUsing Non-Callables as callbacks\r\n---------------------------------\r\n\r\nWhenever we made subscriptions above, we actually simplied things a\r\nlittle bit. The full signature of the method is:\r\n\r\n def subscribe(listener[, method[, reference_retention]])\r\n\r\nLet's explore the `method` argument first. Up to now, we only used\r\nfunction objects as listeners. Basically, in fact, we might have used\r\nany callable object. Remember, that any object is \"callable\" in Python,\r\nif it provides a `__call__` method, so guess, what's the default value\r\nof the `method` argument?\r\n\r\n >>> s1 = some_event.subscribe(printer, method='__call__')\r\n >>> some_event.publish('foo')\r\n ('foo',) {}\r\n ('foo',) {}\r\n >>> s1.cancel()\r\n True\r\n\r\nNothing new. So, now you might ask: when do I use a different method\r\nname?\r\n\r\n >>> class Target(object):\r\n ... def __init__(self, name):\r\n ... self.name = name\r\n ... def _callback(self, *args, **keys):\r\n ... print self.name, args, keys\r\n >>> s1 = some_event.subscribe(Target('foo'))\r\n >>> some_event.publish('Bumm!') #doctest: +ELLIPSIS\r\n Traceback (most recent call last):\r\n ...\r\n TypeError: 'Target' object is not callable\r\n\r\nOops. Let's remove the offender, before someone notices our mistake:\r\n\r\n >>> s1.cancel()\r\n True\r\n >>> s1 = some_event.subscribe(Target('foo'), method='_callback')\r\n >>> some_event.publish('works!')\r\n ('works!',) {}\r\n foo ('works!',) {}\r\n\r\n\r\nReference Retention\r\n--------------------\r\n\r\nSo, that's that. There is still an unexplored argument to `subscribe`\r\nleft, though: `reference_retention`. The name sounds dangerous, but what\r\ndoes it do?\r\n\r\n >>> listener = Target('yummy')\r\n >>> s2 = some_event.subscribe(listener, method='_callback', reference_retention=RR.WEAK)\r\n >>> some_event.publish('yow')\r\n ('yow',) {}\r\n foo ('yow',) {}\r\n yummy ('yow',) {}\r\n\r\nHm. So far, no differences. Let's make a simple change:\r\n\r\n >>> listener = None\r\n >>> some_event.publish('yow')\r\n ('yow',) {}\r\n foo ('yow',) {}\r\n\r\nAh. Ok. Our `yummy` listener is gone. What happened? Well, by specifying\r\na reference retention policy of `WEAK`, we told the publisher, that it should\r\nuse a weak reference to the listener just installed, instead of the default\r\nstrong reference. And after we released the only other known strong \r\nreference to the listener by setting `listener` to `None`, the listener\r\nwas actually removed from the publisher. Note, BTW., that the above example\r\nmay fail with python implementations other than CPython, due to different\r\npolicies with respect to garbage collection. The principle should remain\r\nvalid, though, in Jython as well as IronPython, but in those implementations,\r\nthere is no guarantee, that the listener is removed as soon as the last\r\nreference to it is dropped. \r\n\r\nOf course, this all works too, if the method to be called is the default\r\none: `__call__`:\r\n\r\n >>> def make_listener(name):\r\n ... def listener(*args, **keys):\r\n ... print name, args, keys\r\n ... return listener\r\n >>> listener = make_listener('weak')\r\n >>> s2 = some_event.subscribe(listener, reference_retention=RR.WEAK)\r\n >>> some_event.publish('event')\r\n ('event',) {}\r\n foo ('event',) {}\r\n weak ('event',) {}\r\n >>> listener = None\r\n >>> some_event.publish('event')\r\n ('event',) {}\r\n foo ('event',) {}\r\n\r\nThat's about all there is to know about the library. As I said above: it\r\nis simple, and might not be useful for all scenarioes and use cases, but\r\nit does what it was written to.\r\n\r\n\r\nError handling\r\n----------------\r\n\r\nThe `Publisher` class is not intended to be subclassed. If you need to\r\ntailor the behaviour, you use policy objects/callbacks, which are passed\r\nto the constructor. Right now, there is a single adjustable policy, namely,\r\nthe behaviour of the publisher in case, listeners raise exceptions:\r\n\r\n >>> def toobad(event):\r\n ... if event == 'raise':\r\n ... raise ValueError\r\n >>> s1 = some_event.subscribe(toobad)\r\n >>> some_event.publish('harmless')\r\n ('harmless',) {}\r\n foo ('harmless',) {}\r\n >>> some_event.publish('raise')\r\n Traceback (most recent call last):\r\n ...\r\n ValueError\r\n\r\nAs you can see, the default behaviour is to re-raise the exception\r\nfrom within `publish`. This might not be adequate depending on the use\r\ncase. In particular, it will prevent any listeners registered later\r\nto be run. So, let's define our own error handling:\r\n\r\n >>> def log_error(exception, value, traceback, subscription, args, keys):\r\n ... print \"caught\", exception\r\n >>> publisher = Publisher(exception_handler=log_error)\r\n >>> publisher.subscribe(toobad) #doctest: +ELLIPSIS\r\n \r\n >>> publisher.subscribe(printer) #doctest: +ELLIPSIS\r\n \r\n >>> publisher.publish('harmless')\r\n ('harmless',) {}\r\n >>> publisher.publish('raise')\r\n caught \r\n ('raise',) {}\r\n\r\nAs an alternative to providing the error handler at construction time,\r\nyou may also provide an error handler when publishing an event, like\r\nso:\r\n\r\n >>> def log_error_2(exception, value, traceback, subscription, args, keys):\r\n ... print \"caught\", exception, \"during publication\"\r\n >>> publisher.publish_safely(log_error_2, 'raise')\r\n caught during publication\r\n ('raise',) {}\r\n\r\nAs you can see, the per-call error handler takes precedence over the\r\npublisher's default error handler. Note, that there is no chaining, i.e.,\r\nif the per-call error handler raises an exception, the publisher's default\r\nhandler is *not* called, but the exception is simply propagated outwards\r\nto the caller of `publish_safely`: the publisher has no way to distinguish\r\nbetween exceptions raised because the handler wants to abort the dispatch\r\nand exceptions raised by accident, so all exceptions raised by the handler\r\nare simply forwarded to the client application.\r\n\r\n\r\nThread Safety\r\n==============\r\n\r\nThe library is fully thread aware and thread safe. Thus, subscribing to\r\na listener shared across multiple threads is safe, and so is cancelling\r\nsubscriptions.\r\n\r\n\r\nChanges\r\n========\r\n\r\nVersion 0.4\r\n------------\r\n\r\nSubscription handles now provide access to their listener objects and\r\nmethod names. This was added for the sake of error handling code, which\r\nwants to log exceptions and provide a better way of identifying the \r\nactual listener, which went rogue.\r\n\r\nVersion 0.3\r\n------------\r\n\r\nFixed `setup.py` to properly proclaim the namespace packages used.\r\n\r\nVersion 0.2\r\n------------\r\n\r\nError handling has been changed. Instead of subclassing the publisher,\r\nthe default exception handler is now passed as callback to the publisher \r\nduring construction. The class `Publisher` is now documented as \"not\r\nintended for being subclassed\".", "description_content_type": null, "docs_url": "https://pythonhosted.org/darts.util.events/", "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/deterministic-arts/DartsPyEvents", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "darts.util.events", "package_url": "https://pypi.org/project/darts.util.events/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/darts.util.events/", "project_urls": { "Homepage": "https://github.com/deterministic-arts/DartsPyEvents" }, "release_url": "https://pypi.org/project/darts.util.events/0.4/", "requires_dist": null, "requires_python": null, "summary": "Simple C#-style event dispatcher", "version": "0.4" }, "last_serial": 737712, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "9fab4a6b0b08e2fcfd568c39737b2061", "sha256": "b3ba486e73bd36374ead0d41e50fc8c89acec7ea237e27efca62ea9361b2763c" }, "downloads": -1, "filename": "darts.util.events-0.1.tar.gz", "has_sig": false, "md5_digest": "9fab4a6b0b08e2fcfd568c39737b2061", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8287, "upload_time": "2010-12-01T17:55:54", "url": "https://files.pythonhosted.org/packages/30/ea/b9b044720866755aa92597bc14df1dbc3e535647e8d69eab120491e7a18a/darts.util.events-0.1.tar.gz" } ], "0.2": [ { "comment_text": "", "digests": { "md5": "4a2bf463ae0fb14011ac745b96a925d5", "sha256": "9a2deb286ba22446a541585c515331c37d28ef25f0235aa5227fc372ac2177db" }, "downloads": -1, "filename": "darts.util.events-0.2.tar.gz", "has_sig": false, "md5_digest": "4a2bf463ae0fb14011ac745b96a925d5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8635, "upload_time": "2010-12-13T17:57:49", "url": "https://files.pythonhosted.org/packages/53/0f/83fd0c013e7b3220515cc9ebd7a2ecfc617b899274de2691b9139e975bad/darts.util.events-0.2.tar.gz" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "c35dfaf21b945f4f046d4776299000d2", "sha256": "6ec4529223f3c0a04a4303025fcf67721838ee65d8cbe6a756007c37e63f95d7" }, "downloads": -1, "filename": "darts.util.events-0.3.tar.gz", "has_sig": false, "md5_digest": "c35dfaf21b945f4f046d4776299000d2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16010, "upload_time": "2011-01-19T16:28:43", "url": "https://files.pythonhosted.org/packages/2a/58/baea937c29299550e2635d12f45d1edf23591b362c1b2a3a01059e05e404/darts.util.events-0.3.tar.gz" } ], "0.4": [ { "comment_text": "", "digests": { "md5": "bea09eaef990f7fdc53850d5cb06c617", "sha256": "bf0a70d8d67af15c95e62f5596c97cdf76dc068bb1cdae0f9a4094eb5ee33579" }, "downloads": -1, "filename": "darts.util.events-0.4.tar.gz", "has_sig": false, "md5_digest": "bea09eaef990f7fdc53850d5cb06c617", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18720, "upload_time": "2011-10-05T11:19:53", "url": "https://files.pythonhosted.org/packages/0f/5e/81fefc75f670847d2d495662cb20e5531d7d43ed126441f44bbe27aad43a/darts.util.events-0.4.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "bea09eaef990f7fdc53850d5cb06c617", "sha256": "bf0a70d8d67af15c95e62f5596c97cdf76dc068bb1cdae0f9a4094eb5ee33579" }, "downloads": -1, "filename": "darts.util.events-0.4.tar.gz", "has_sig": false, "md5_digest": "bea09eaef990f7fdc53850d5cb06c617", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18720, "upload_time": "2011-10-05T11:19:53", "url": "https://files.pythonhosted.org/packages/0f/5e/81fefc75f670847d2d495662cb20e5531d7d43ed126441f44bbe27aad43a/darts.util.events-0.4.tar.gz" } ] }