From 971fba31734e4bf78a2cd80fadd6a6c8d1f391b5 Mon Sep 17 00:00:00 2001 From: Benjamin Braatz Date: Tue, 10 Nov 2020 22:56:30 +0100 Subject: [PATCH 1/1] Initial commit of version 0.1.0 --- .gitignore | 1 + LICENSE | 19 ++++++++++ README.md | 7 ++++ doc/index.md | 31 +++++++++++++++ example.py | 39 +++++++++++++++++++ graphit/__init__.py | 0 graphit/event.py | 91 +++++++++++++++++++++++++++++++++++++++++++++ setup.py | 26 +++++++++++++ 8 files changed, 214 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 doc/index.md create mode 100755 example.py create mode 100644 graphit/__init__.py create mode 100644 graphit/event.py create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..86da0a6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Graph-IT GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..339bb40 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Event Module + +This is a small Python module that provides classes with the possibility +to register and emit events. + +A more detailed documentation can be found in [doc/index.md](doc/index.md), +which can also be found at [http://docs.graph-it.com/graphit/event-py](http://docs.graph-it.com/graphit/event-py). diff --git a/doc/index.md b/doc/index.md new file mode 100644 index 0000000..eca3edc --- /dev/null +++ b/doc/index.md @@ -0,0 +1,31 @@ +# Event Module + +This is a small Python module that provides classes with the possibility +to register and emit events. + +## Installation + +The event module can be installed with `pip` directly from our git repository: +```sh +$ pip install git+git://git.graph-it.com/graphit/event-py.git +``` + +## Usage + +The module provides an abstract `EventEmitterInterface` and a class +`EventEmitterMixin` implementing it. + +Classes that want to emit events should extend the `EventEmitterMixin` and +call `self._emit(event, *args, **kwargs)` at appropriate locations in their +code. +The designations of the events can be any kind of hashables. + +Listener callbacks can then be registered with the `on(event, callback)` +method. +The callbacks should receive exactly the arguments provided in the +`self._emit` calls. +A handle to the registration is returned by the call to `on`, which can be +used to unregister the callback again by the `off(handle)` method. + +A simple example can be found in the `example.py` script in the git +repositiory. diff --git a/example.py b/example.py new file mode 100755 index 0000000..203d1b1 --- /dev/null +++ b/example.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +from graphit.event import EventEmitterMixin + + +class ExampleEmitter(EventEmitterMixin): + + def emitting_function(self): + self._emit('ev1', 'ham') + self._emit('ev2') + self._emit('ev3', 42) + + +def cb11(message: str): + print(f"cb11 reveived '{message}'.") + + +def cb12(message: str): + print(f"cb12 reveived '{message}'.") + + +def cb2(): + print("cb2 called") + + +def cb3(value: int): + print(f"cb3 received value {value}.") + + +emitter = ExampleEmitter() +handle11 = emitter.on('ev1', cb11) +handle12 = emitter.on('ev1', cb12) +handle2 = emitter.on('ev2', cb2) +handle3 = emitter.on('ev3', cb3) +print("cb11, cb12, cb2 and cb3 are on.") +emitter.emitting_function() +emitter.off(handle12) +emitter.off(handle2) +print("cb12 and cb2 are off, cb11 and cb3 are still on.") +emitter.emitting_function() diff --git a/graphit/__init__.py b/graphit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graphit/event.py b/graphit/event.py new file mode 100644 index 0000000..67c677e --- /dev/null +++ b/graphit/event.py @@ -0,0 +1,91 @@ +__all__ = ('EventEmitterInterface', 'EventEmitterMixin') + + +import abc +import uuid + +from typing import Hashable, Callable, MutableMapping, Mapping + +EvMap = MutableMapping[Hashable, Hashable] +CbMap = MutableMapping[Hashable, MutableMapping[Hashable, Callable]] + + +class EventEmitterInterface(abc.ABC): + + @abc.abstractmethod + def on(self, event: Hashable, callback: Callable) -> Hashable: + ''' + Registers the given callback for the given event. + Returns handle to unregister the given callback. + ''' + + @abc.abstractmethod + def off(self, handle: Hashable) -> bool: + ''' + Unregisters a previously registered callback by the given handle. + Returns True on success. + ''' + + @abc.abstractmethod + def _emit(self, event: Hashable, *args, **kwargs) -> None: + ''' + Emits the given event by calling all callbacks registered for this + event. + ''' + + +class EventEmitterMixin(EventEmitterInterface): + def on(self, event: Hashable, callback: Callable) -> Hashable: + events: EvMap + callbacks: CbMap + try: + events = self._eventEmitterMixinEvents + callbacks = self._eventEmitterMixinCallbacks + except AttributeError: + self._eventEmitterMixinEvents: EvMap = {} + self._eventEmitterMixinCallbacks: CbMap = {} + events = self._eventEmitterMixinEvents + callbacks = self._eventEmitterMixinCallbacks + + if event not in callbacks: + callbacks[event] = {} + + handle = uuid.uuid4() + while handle in events: + handle = uuid.uuid4() + + events[handle] = event + callbacks[event][handle] = callback + + return handle + + def off(self, handle: Hashable) -> bool: + try: + events = self._eventEmitterMixinEvents + callbacks = self._eventEmitterMixinCallbacks + except AttributeError: + return False + + if handle not in events: + return False + event = events[handle] + + del events[handle] + del callbacks[event][handle] + + if not callbacks[event]: + del callbacks[event] + + return True + + def _emit(self, event: Hashable, *args, **kwargs) -> None: + try: + callbacks = self._eventEmitterMixinCallbacks + except AttributeError: + return + + if event not in callbacks: + return + + for callback in callbacks[event].values(): + callback(*args, **kwargs) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3097938 --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +import setuptools + +with open("README.md", "r") as readme_file: + long_description = readme_file.read() + +setuptools.setup( + name="graphit-event", + version="0.1.0", + author="Graph-IT GmbH", + author_email="info@graph-it.com", + description="Event Module", + long_description=long_description, + long_description_content_type="text/markdown", + url="http://docs.graph-it.com/graphit/event-py", + packages=setuptools.find_packages(), + setup_requires=[ + "wheel" + ], + install_requires=[ + ], + classifiers=[ + "Programming Language :: Python", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], +) -- 2.34.1