obsub-0.2/0000755000175000001440000000000012302107555013110 5ustar eduardusers00000000000000obsub-0.2/setup.py0000755000175000001440000000210712274701603014627 0ustar eduardusers00000000000000#!/usr/bin/env python import logging from setuptools import setup # Create a front page for PyPI: long_description = None try: long_description = open('README.rst').read() long_description += '\n' + open('CHANGELOG.rst').read() except IOError: # some file is not available # just use what we got so far but issue a warning logging.warn('documentation files missing') setup( name='obsub', version='0.2', description='Implementation of the observer pattern via a decorator', long_description=long_description, author='Eduard Bopp', author_email='eduard.bopp@aepsil0n.de', url='https://github.com/aepsil0n/obsub', py_modules=['obsub',], classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Software Development', ], license='Public Domain', tests_require='nose', test_suite='nose.collector', ) obsub-0.2/LICENSE.rst0000644000175000001440000001624112274675436014750 0ustar eduardusers00000000000000This Python module is licensed under a `CC0 license `__. To the extent possible under law, Eduard Bopp has waived all copyright and related or neighboring rights to obsub. This work is published from: Germany. Full license text ================= CC0 1.0 Universal ----------------- *CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER.* Statement of Purpose ~~~~~~~~~~~~~~~~~~~~ The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: - the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; - moral rights retained by the original author(s) and/or performer(s); - publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; - rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; - rights protecting the extraction, dissemination, use and reuse of data in a Work; - database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and - other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. ~~~~~~~~~~ To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. obsub-0.2/setup.cfg0000644000175000001440000000020312302107555014724 0ustar eduardusers00000000000000[nosetests] with-doctest = 1 doctest-extension = doctest exclude = py3 [egg_info] tag_svn_revision = 0 tag_build = tag_date = 0 obsub-0.2/test/0000755000175000001440000000000012302107555014067 5ustar eduardusers00000000000000obsub-0.2/test/test_weakref.py0000644000175000001440000000376512262024041017130 0ustar eduardusers00000000000000''' These tests that the event handling mechanism does not produce memory leaks. This could in principle happen, since it introduces a cyclic dependency that might prevent garbage collection. ''' import weakref import gc from obsub import event def test_memory_leak(): # Define a test class and an event handler class A(object): @event def on_blubb(self): pass def handler(self): pass # Instantiate and attach event handler a = A() a.on_blubb += handler # Weak reference for testing wr = weakref.ref(a) # At first, weak reference exists assert wr() is not None # (implicitly) delete the A-instance by reassigning the only hard-ref. # This is equivalent to `del a` but serves the purpose to demonstrate # that there are very subtle ways to delete an instance: a = None # Trigger the garbage collection manually gc.collect() # after deletion it should be dead assert wr() is None def test_object_stays_alive_during_handler_execution(): # Define a test class and event handlers class A(object): @event def on_blubb(self): pass deleted = False class B(object): def __init__(self, a): # capture the only hard-ref on the A-instance: self.a = a self.a.on_blubb += self.handler def handler(self, a): # delete the A-instance del self.a a.deleted = True del a gc.collect() def handler(a): # We want a valid reference to the handled object, ... assert a is not None # ..., even if the deletion handler has already been called: assert a.deleted # Instantiate and attach event handler b = B(A()) b.a.on_blubb += handler wr = weakref.ref(b.a) b.a.on_blubb() # Trigger the garbage collection manually gc.collect() # make sure, b.a has been deleted after event handling assert wr() is None obsub-0.2/test/test_core.py0000644000175000001440000000772312274675436016461 0ustar eduardusers00000000000000""" Test the core functionality. Contains a converted version of all of the doctests. """ # test utilities import unittest import weakref, gc # tested module from obsub import event class NewStyle(object): def __init__(self): self.count = 0 @event def emit(self, first, second): self.count += 1 class OldStyle: def __init__(self): self.count = 0 @event def emit(self, first, second): self.count += 1 class Observer(object): def __init__(self, call_stack): self.call_stack = call_stack def __call__(self, source, first, second): diff = (sum(1 for call in self.call_stack if call[2] == source) - source.count) + 1 self.call_stack.append((self, diff, source, first, second)) class TestCore(unittest.TestCase): """Test the obsub module for new style classes.""" cls = NewStyle def setUp(self): self.call_stack = [] self.maxDiff = None def observer(self, instance): observer = Observer(self.call_stack) instance.emit += observer return observer def check_stack(self, expected): self.assertEqual(expected, self.call_stack) def test_single_handler(self): """A single handler is invoked correctly.""" src = self.cls() obs = self.observer(src) src.emit("Hello", "World") self.check_stack([(obs, 0, src, "Hello", "World")]) def test_remove_handler(self): """Removal of event handlers works correctly.""" src = self.cls() obs = self.observer(src) src.emit -= obs src.emit("something", "arbitrary") self.check_stack([]) def test_multiple_handlers(self): """Multiple handlers are invoked in correct order.""" src = self.cls() obs = [self.observer(src) for i in range(10)] src.emit("Hello", "World") self.check_stack([(obs[i], i, src, "Hello", "World") for i in range(10)]) def test_multiple_sources(self): """Multiple instances of the event source class can coexist.""" src = [self.cls() for i in range(10)] obs = [self.observer(s) for s in src] for s in src: s.emit("Hello", "World") self.check_stack([(obs[i], 0, src[i], "Hello", "World") for i in range(10)]) def test_keyword_arguments(self): """Keyword arguments can be used.""" src = self.cls() obs = self.observer(src) src.emit(second="World", first="Hello") self.check_stack([(obs, 0, src, "Hello", "World")]) def test_wrong_signature(self): """Any incorrect call signature raises a TypeError.""" src = self.cls() self.assertRaises(TypeError, src.emit, "Hello") self.assertRaises(TypeError, src.emit, first="Hello", third="!") self.assertRaises(TypeError, src.emit, second="World") obs = self.observer(src) self.assertRaises(TypeError, src.emit, forth=":)") self.check_stack([]) def test_class_based_access(self): """The emitter function can be invoked via its class.""" src = self.cls() obs = self.observer(src) self.cls.emit(src, "Hello", "World") self.check_stack([(obs, 0, src, "Hello", "World")]) def test_keep_alive(self): """A reference to a bound event method keeps its owner instance alive.""" src = self.cls() obs = self.observer(src) emit = src.emit # remove original reference: wr = weakref.ref(src) del src gc.collect() # the emitter function is still functioning: assert wr() is not None emit("Hello", "World") self.check_stack([(obs, 0, wr(), "Hello", "World")]) # removing the bound method makes the instance disappear: del emit self.call_stack[0] = None gc.collect() assert wr() is None # ERRORS: class TestCoreOldStyle(TestCore): cls = OldStyle obsub-0.2/test/test_signature.py0000644000175000001440000000032712263024600017476 0ustar eduardusers00000000000000''' Test that the decorator correctly preserves the signature. Currently, this is only possible for python3. ''' import sys try: from test.py3.test_signature import * except ImportError: __test__ = False obsub-0.2/test/py3/0000755000175000001440000000000012302107555014602 5ustar eduardusers00000000000000obsub-0.2/test/py3/test_signature.py0000644000175000001440000000144312263024600020211 0ustar eduardusers00000000000000''' Test that the decorator correctly preserves the signature. Currently, this is only possible for python3. ''' from obsub import event try: from inspect import signature except ImportError: raise NotImplementedError referenced = {} def on_blubb( self:0, obscure:"NOTE: default argument is a by reference: "=referenced, *, with_kwonlyarg:int, **kwargs): return obscure class A(object): on_blubb = event(on_blubb) def test_signature(): # Define a test class and an event handler a = A() # signature is preserved: (!!) assert signature(a.on_blubb) == signature(on_blubb) # NOTE: we even got the exact object as default parameter, not only an # exact copy: assert a.on_blubb(with_kwonlyarg="xyz") is referenced obsub-0.2/MANIFEST.in0000644000175000001440000000012512274675436014664 0ustar eduardusers00000000000000include README.rst CHANGELOG.rst include LICENSE.rst recursive-include test/py3 *.py obsub-0.2/PKG-INFO0000644000175000001440000001646212302107555014216 0ustar eduardusers00000000000000Metadata-Version: 1.1 Name: obsub Version: 0.2 Summary: Implementation of the observer pattern via a decorator Home-page: https://github.com/aepsil0n/obsub Author: Eduard Bopp Author-email: eduard.bopp@aepsil0n.de License: Public Domain Description: obsub ===== |Build Status| |Coverage| |Version| |Downloads| |License| Small python module that implements the observer pattern via a decorator. Description ----------- This is based on a `thread on stackoverflow `_ (the example of C#-like events by Jason Orendorff), so I don't take any credit for the idea. I merely made a fancy module with documentation and tests out of it, since I needed it in a bigger project. It is quite handy and I've been using it in a couple of projects, which require some sort of event handling. Thus it is `licensed as CC0 `__, so basically do-whatever-you-want to the extent legally possible. Installation ------------ *obsub* is available on PyPI, so you can simply install it using ``pip install obsub`` or you do it manually using ``setup.py`` as with any python package. Usage ----- The ``event`` decorator from the ``obsub`` module is used as follows: .. code:: python from obsub import event # Define a class with an event class Subject(object): @event def on_stuff(self, arg): print('Stuff {} happens'.format(arg)) # Now define an event handler, the observer def handler(subject, arg): print('Stuff {} is handled'.format(arg)) # Wire everything up... sub = Subject() sub.on_stuff += handler # And try it! sub.on_stuff('foo') You should now get both print messages from the event itself and the event handler function, like so: :: Stuff foo happens Stuff foo is handled Continuous integration ---------------------- For the fun of it, `Travis CI `__ is used for continuous integration. As long as everything is fine, the button below should be green and shiny! |Build Status| The continuous integration ensures that our tests run on the following platforms: - Python 2.6, 2.7 - Python 3.2, 3.3 - pypy It might also work on Python 2.5, but is not automatically tested with this version. We also track the coverage of our tests with coveralls.io |Coverage| Use `coverage `__ to generate local coverage reports like this: :: coverage run setup.py nosetests Note: on some platforms (e.g. Ubuntu) the executable is called ``python-coverage``. Contribution and feedback ------------------------- *obsub* is developed on `github `__. If you have any questions about this software or encounter bugs, you're welcome to open a `new issue on github `__. In case you do not want to use github for some reason, you can alternatively send an email one of us: - `Eduard Bopp `__ - `André-Patrick Bubel `__ - `Thomas Gläßle `__ Feel free to contribute patches as pull requests as you see fit. Try to be consistent with PEP 8 guidelines as far as possible and test everything. Otherwise, your commit messages should start with a capitalized verb for consistency. Unless your modification is completely trivial, also add a message body to your commit. Credits ------- Thanks to Jason Orendorff on for the idea on stackoverflow. I also want to thank @coldfix and @Moredread for contributions and feedback. .. |Downloads| image:: https://pypip.in/d/obsub/badge.png :target: https://pypi.python.org/pypi/obsub/ :alt: Downloads .. |Version| image:: https://pypip.in/v/obsub/badge.png :target: https://pypi.python.org/pypi/obsub/ :alt: Latest Version .. |License| image:: https://pypip.in/license/obsub/badge.png :target: https://pypi.python.org/pypi/obsub/ :alt: License .. |Build Status| image:: https://api.travis-ci.org/aepsil0n/obsub.png?branch=master :target: https://travis-ci.org/aepsil0n/obsub .. |Coverage| image:: https://coveralls.io/repos/aepsil0n/obsub/badge.png?branch=master :target: https://coveralls.io/r/aepsil0n/obsub Changelog --------- v0.2 ~~~~ From a user perspective the preservation of function signatures and a couple of bug fixes are probably most relevant. Python 2.5 is no longer tested by continuous integration, though we try to avoid unnecessary changes that might break backwards compatibility. In addition, there are quite a number of changes that mostly concern developers. - Function signatures are now preserved correctly by the event decorator. This is true only for python3. On python2 there is no support for default arguments, currently - Some fixes to memory handling and tests thereof. This includes a more generic handling of the garbage collection process within the test suite to make it pass on pypy, too. - Massive refactoring of test suite from one very long doctest to more focussed unit tests. - The documentation has been converted from Markdown to reStructuredText, since it is compatible with both PyPI and GitHub. - Various improvements and some streamlining of the documentation. - Fix package name in license. - Continuous integration now includes coveralls.io support. - Support for Python 2.5 is no longer tested using Travis CI, since they have dropped support for this version. v0.1.1 ~~~~~~ - Add __all__ attribute to module - Fix a couple of documentation issues v0.1 ~~~~ *Initial release* Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Software Development obsub-0.2/CHANGELOG.rst0000644000175000001440000000247512274675436015161 0ustar eduardusers00000000000000Changelog --------- v0.2 ~~~~ From a user perspective the preservation of function signatures and a couple of bug fixes are probably most relevant. Python 2.5 is no longer tested by continuous integration, though we try to avoid unnecessary changes that might break backwards compatibility. In addition, there are quite a number of changes that mostly concern developers. - Function signatures are now preserved correctly by the event decorator. This is true only for python3. On python2 there is no support for default arguments, currently - Some fixes to memory handling and tests thereof. This includes a more generic handling of the garbage collection process within the test suite to make it pass on pypy, too. - Massive refactoring of test suite from one very long doctest to more focussed unit tests. - The documentation has been converted from Markdown to reStructuredText, since it is compatible with both PyPI and GitHub. - Various improvements and some streamlining of the documentation. - Fix package name in license. - Continuous integration now includes coveralls.io support. - Support for Python 2.5 is no longer tested using Travis CI, since they have dropped support for this version. v0.1.1 ~~~~~~ - Add __all__ attribute to module - Fix a couple of documentation issues v0.1 ~~~~ *Initial release* obsub-0.2/obsub.py0000644000175000001440000001565412274675436014627 0ustar eduardusers00000000000000''' This is an implementation of the observer pattern. It uses function decorators to achieve the desired event registration mechanism. For further reference, see http://en.wikipedia.org/wiki/Observer_pattern The idea is based on this thread: http://stackoverflow.com/questions/1904351/python-observer-pattern-examples-tips ''' import functools import inspect try: # use python3 signatures if available # this takes care of enforcing the correct signature at call time and # provides the correct default arguments from inspect import signature except ImportError: # pragma: no cover # python2 has no support for signatures def signature(fn): return None __all__ = ['event'] __version__ = '0.2' class event(object): ''' This class serves as a utility to decorate a function as an event. The following example demonstrates its functionality in an abstract way. A class method can be decorated as follows: >>> class A(object): ... def __init__(self, name): ... self.name = name ... ... @event ... def progress(self, first, second): ... print("Doing something...") A.progress is the event. It is triggered after executing the code in the decorated progress routine. Now that we have a class with some event, let's create an event handler. >>> def handler(self, first, second): ... print("%s %s and %s!" % (first, self.name, second)) Note that the handler (and signal calls) must have the signature defined by the decorated event method. This handler only greets the object that triggered the event by using its name attribute. Let's create some instances of A and register our new event handler to their progress event. >>> a = A("Foo") >>> b = A("Bar") >>> a.progress += handler >>> b.progress += handler Now everything has been setup. When we call the method, the event will be triggered: >>> a.progress("Hello", "World") Doing something... Hello Foo and World! >>> b.progress(second="Others", first="Hi") Doing something... Hi Bar and Others! What happens if we disobey the call signature? >>> c = A("World") >>> c.progress(second="World") # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): TypeError: progress() missing 1 required positional argument: 'first' Class based access is possible as well: >>> A.progress(a, "Hello", "Y") Doing something... Hello Foo and Y! Bound methods keep the instance alive: >>> f = a.progress >>> import weakref, gc >>> wr = weakref.ref(a) >>> del a >>> c=gc.collect() >>> assert wr() is not None >>> f("Hi", "Z") Doing something... Hi Foo and Z! If we delete the hard reference to the bound method and run the garbage collector (to make sure it is run at all), the object will be gone: >>> del f >>> c=gc.collect() >>> assert wr() is None ''' def __init__(self, function): ''' Constructor. * function -- The function to be wrapped by the decorator. ''' # Copy docstring and other attributes from function functools.update_wrapper(self, function) self.__signature__ = signature(function) # Used to enforce call signature even when no slot is connected. # Can also execute code (called before handlers) self.__function = function def __set__(self, instance, value): ''' This is a NOP preventing that a boundevent instance is stored. This prevents operations like `a.progress += handler` to have side effects that result in a cyclic reference. http://stackoverflow.com/questions/18287336/memory-leak-when-invoking-iadd-via-get-without-using-temporary ''' pass def __get__(self, instance, owner): ''' Overloaded __get__ method. Defines the object resulting from a method/function decorated with @event. See http://docs.python.org/reference/datamodel.html?highlight=__get__#object.__get__ for a detailed explanation of what this special method usually does. * instance -- The instance of event invoked. * owner -- The owner class. ''' # this case corresponds to access via the owner class: if instance is None: @functools.wraps(self.__function) def wrapper(instance, *args, **kwargs): return self.__get__(instance, owner)(*args, **kwargs) else: wrapper = functools.wraps(self.__function)(boundevent(instance, self.__function)) wrapper.__signature__ = self.__signature__ return wrapper class boundevent(object): '''Private helper class for event system.''' def __init__(self, instance, function): ''' Constructor. * instance -- the instance whose member the event is ''' self.instance = instance self.__function = function self.__key = ' ' + function.__name__ @property def __event_handlers(self): if self.__key not in self.instance.__dict__: self.instance.__dict__[self.__key] = [] return self.instance.__dict__[self.__key] def __iadd__(self, function): ''' Overloaded += operator. It registers event handlers to the event. * function -- The right-hand-side argument of the operator; this is the event handling function that registers to the event. ''' # Add the function as a new event handler self.__event_handlers.append(function) # Return the boundevent instance itself for coherent syntax behaviour return self def __isub__(self, function): ''' Overloaded -= operator. It removes registered event handlers from the event. * function -- The right-hand-side argument of the operator; this is the function that needs to be removed from the list of event handlers. ''' # Remove the function from the list of registered event handlers self.__event_handlers.remove(function) # Return the boundevent instance itself for coherent syntax behaviour return self def __call__(self, *args, **kwargs): ''' Overloaded call method; it defines the behaviour of boundevent(). When the event is called, all registered event handlers are called. * *args -- Arguments given to the event handlers. * **kwargs -- Keyword arguments given to the event handlers. ''' # Enforce signature and possibly execute entry code. This makes sure # any inconsistent call will be caught immediately, independent of # connected handlers. result = self.__function(self.instance, *args, **kwargs) # Call all registered event handlers for f in self.__event_handlers[:]: f(self.instance, *args, **kwargs) return result obsub-0.2/README.rst0000644000175000001440000000775012274675436014630 0ustar eduardusers00000000000000obsub ===== |Build Status| |Coverage| |Version| |Downloads| |License| Small python module that implements the observer pattern via a decorator. Description ----------- This is based on a `thread on stackoverflow `_ (the example of C#-like events by Jason Orendorff), so I don't take any credit for the idea. I merely made a fancy module with documentation and tests out of it, since I needed it in a bigger project. It is quite handy and I've been using it in a couple of projects, which require some sort of event handling. Thus it is `licensed as CC0 `__, so basically do-whatever-you-want to the extent legally possible. Installation ------------ *obsub* is available on PyPI, so you can simply install it using ``pip install obsub`` or you do it manually using ``setup.py`` as with any python package. Usage ----- The ``event`` decorator from the ``obsub`` module is used as follows: .. code:: python from obsub import event # Define a class with an event class Subject(object): @event def on_stuff(self, arg): print('Stuff {} happens'.format(arg)) # Now define an event handler, the observer def handler(subject, arg): print('Stuff {} is handled'.format(arg)) # Wire everything up... sub = Subject() sub.on_stuff += handler # And try it! sub.on_stuff('foo') You should now get both print messages from the event itself and the event handler function, like so: :: Stuff foo happens Stuff foo is handled Continuous integration ---------------------- For the fun of it, `Travis CI `__ is used for continuous integration. As long as everything is fine, the button below should be green and shiny! |Build Status| The continuous integration ensures that our tests run on the following platforms: - Python 2.6, 2.7 - Python 3.2, 3.3 - pypy It might also work on Python 2.5, but is not automatically tested with this version. We also track the coverage of our tests with coveralls.io |Coverage| Use `coverage `__ to generate local coverage reports like this: :: coverage run setup.py nosetests Note: on some platforms (e.g. Ubuntu) the executable is called ``python-coverage``. Contribution and feedback ------------------------- *obsub* is developed on `github `__. If you have any questions about this software or encounter bugs, you're welcome to open a `new issue on github `__. In case you do not want to use github for some reason, you can alternatively send an email one of us: - `Eduard Bopp `__ - `André-Patrick Bubel `__ - `Thomas Gläßle `__ Feel free to contribute patches as pull requests as you see fit. Try to be consistent with PEP 8 guidelines as far as possible and test everything. Otherwise, your commit messages should start with a capitalized verb for consistency. Unless your modification is completely trivial, also add a message body to your commit. Credits ------- Thanks to Jason Orendorff on for the idea on stackoverflow. I also want to thank @coldfix and @Moredread for contributions and feedback. .. |Downloads| image:: https://pypip.in/d/obsub/badge.png :target: https://pypi.python.org/pypi/obsub/ :alt: Downloads .. |Version| image:: https://pypip.in/v/obsub/badge.png :target: https://pypi.python.org/pypi/obsub/ :alt: Latest Version .. |License| image:: https://pypip.in/license/obsub/badge.png :target: https://pypi.python.org/pypi/obsub/ :alt: License .. |Build Status| image:: https://api.travis-ci.org/aepsil0n/obsub.png?branch=master :target: https://travis-ci.org/aepsil0n/obsub .. |Coverage| image:: https://coveralls.io/repos/aepsil0n/obsub/badge.png?branch=master :target: https://coveralls.io/r/aepsil0n/obsub obsub-0.2/obsub.egg-info/0000755000175000001440000000000012302107555015714 5ustar eduardusers00000000000000obsub-0.2/obsub.egg-info/SOURCES.txt0000644000175000001440000000043112302107555017576 0ustar eduardusers00000000000000CHANGELOG.rst LICENSE.rst MANIFEST.in README.rst obsub.py setup.cfg setup.py obsub.egg-info/PKG-INFO obsub.egg-info/SOURCES.txt obsub.egg-info/dependency_links.txt obsub.egg-info/top_level.txt test/test_core.py test/test_signature.py test/test_weakref.py test/py3/test_signature.pyobsub-0.2/obsub.egg-info/PKG-INFO0000644000175000001440000001646212302107555017022 0ustar eduardusers00000000000000Metadata-Version: 1.1 Name: obsub Version: 0.2 Summary: Implementation of the observer pattern via a decorator Home-page: https://github.com/aepsil0n/obsub Author: Eduard Bopp Author-email: eduard.bopp@aepsil0n.de License: Public Domain Description: obsub ===== |Build Status| |Coverage| |Version| |Downloads| |License| Small python module that implements the observer pattern via a decorator. Description ----------- This is based on a `thread on stackoverflow `_ (the example of C#-like events by Jason Orendorff), so I don't take any credit for the idea. I merely made a fancy module with documentation and tests out of it, since I needed it in a bigger project. It is quite handy and I've been using it in a couple of projects, which require some sort of event handling. Thus it is `licensed as CC0 `__, so basically do-whatever-you-want to the extent legally possible. Installation ------------ *obsub* is available on PyPI, so you can simply install it using ``pip install obsub`` or you do it manually using ``setup.py`` as with any python package. Usage ----- The ``event`` decorator from the ``obsub`` module is used as follows: .. code:: python from obsub import event # Define a class with an event class Subject(object): @event def on_stuff(self, arg): print('Stuff {} happens'.format(arg)) # Now define an event handler, the observer def handler(subject, arg): print('Stuff {} is handled'.format(arg)) # Wire everything up... sub = Subject() sub.on_stuff += handler # And try it! sub.on_stuff('foo') You should now get both print messages from the event itself and the event handler function, like so: :: Stuff foo happens Stuff foo is handled Continuous integration ---------------------- For the fun of it, `Travis CI `__ is used for continuous integration. As long as everything is fine, the button below should be green and shiny! |Build Status| The continuous integration ensures that our tests run on the following platforms: - Python 2.6, 2.7 - Python 3.2, 3.3 - pypy It might also work on Python 2.5, but is not automatically tested with this version. We also track the coverage of our tests with coveralls.io |Coverage| Use `coverage `__ to generate local coverage reports like this: :: coverage run setup.py nosetests Note: on some platforms (e.g. Ubuntu) the executable is called ``python-coverage``. Contribution and feedback ------------------------- *obsub* is developed on `github `__. If you have any questions about this software or encounter bugs, you're welcome to open a `new issue on github `__. In case you do not want to use github for some reason, you can alternatively send an email one of us: - `Eduard Bopp `__ - `André-Patrick Bubel `__ - `Thomas Gläßle `__ Feel free to contribute patches as pull requests as you see fit. Try to be consistent with PEP 8 guidelines as far as possible and test everything. Otherwise, your commit messages should start with a capitalized verb for consistency. Unless your modification is completely trivial, also add a message body to your commit. Credits ------- Thanks to Jason Orendorff on for the idea on stackoverflow. I also want to thank @coldfix and @Moredread for contributions and feedback. .. |Downloads| image:: https://pypip.in/d/obsub/badge.png :target: https://pypi.python.org/pypi/obsub/ :alt: Downloads .. |Version| image:: https://pypip.in/v/obsub/badge.png :target: https://pypi.python.org/pypi/obsub/ :alt: Latest Version .. |License| image:: https://pypip.in/license/obsub/badge.png :target: https://pypi.python.org/pypi/obsub/ :alt: License .. |Build Status| image:: https://api.travis-ci.org/aepsil0n/obsub.png?branch=master :target: https://travis-ci.org/aepsil0n/obsub .. |Coverage| image:: https://coveralls.io/repos/aepsil0n/obsub/badge.png?branch=master :target: https://coveralls.io/r/aepsil0n/obsub Changelog --------- v0.2 ~~~~ From a user perspective the preservation of function signatures and a couple of bug fixes are probably most relevant. Python 2.5 is no longer tested by continuous integration, though we try to avoid unnecessary changes that might break backwards compatibility. In addition, there are quite a number of changes that mostly concern developers. - Function signatures are now preserved correctly by the event decorator. This is true only for python3. On python2 there is no support for default arguments, currently - Some fixes to memory handling and tests thereof. This includes a more generic handling of the garbage collection process within the test suite to make it pass on pypy, too. - Massive refactoring of test suite from one very long doctest to more focussed unit tests. - The documentation has been converted from Markdown to reStructuredText, since it is compatible with both PyPI and GitHub. - Various improvements and some streamlining of the documentation. - Fix package name in license. - Continuous integration now includes coveralls.io support. - Support for Python 2.5 is no longer tested using Travis CI, since they have dropped support for this version. v0.1.1 ~~~~~~ - Add __all__ attribute to module - Fix a couple of documentation issues v0.1 ~~~~ *Initial release* Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Software Development obsub-0.2/obsub.egg-info/top_level.txt0000644000175000001440000000000612302107555020442 0ustar eduardusers00000000000000obsub obsub-0.2/obsub.egg-info/dependency_links.txt0000644000175000001440000000000112302107555021762 0ustar eduardusers00000000000000