Initial commit of version 0.1.0
authorBenjamin Braatz <benjamin.braatz@graph-it.com>
Tue, 10 Nov 2020 21:56:30 +0000 (22:56 +0100)
committerBenjamin Braatz <benjamin.braatz@graph-it.com>
Tue, 10 Nov 2020 21:56:30 +0000 (22:56 +0100)
.gitignore [new file with mode: 0644]
LICENSE [new file with mode: 0644]
README.md [new file with mode: 0644]
doc/index.md [new file with mode: 0644]
example.py [new file with mode: 0755]
graphit/__init__.py [new file with mode: 0644]
graphit/event.py [new file with mode: 0644]
setup.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..c18dd8d
--- /dev/null
@@ -0,0 +1 @@
+__pycache__/
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
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 (file)
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 (file)
index 0000000..eca3edc
--- /dev/null
@@ -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 (executable)
index 0000000..203d1b1
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/graphit/event.py b/graphit/event.py
new file mode 100644 (file)
index 0000000..67c677e
--- /dev/null
@@ -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 (file)
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",
+    ],
+)