# Simplevent
## Summary
Simplevent is a simple Event framework for Python, loosely based on the Observer design pattern. The package is minimal:
it defines the `Event` base class and the `SignedEvent` and `NamedEvent` subclasses. An instance of either encapsulates
a `list` but **will also itself behave somewhat like a `list`**; this is essentially an _indirection_.
## Observer Pattern
Simplevent's tiny framework can be seen as a variation on the [Observer Pattern](https://en.wikipedia.org/wiki/Observer_pattern):
- When you instantiate an `Event`, that instance's context (scope) is the `subject`.
- When you subscribe an object to an `Event`, that object is an `observer`.
- When you `invoke` an `Event` instance, you're notifying all `observers` that the `subject` has executed an important action.
## Motivation
Simplevent was a creation inspired by C# and its event framework that's already built into the language. The lack of a
similar system in Python can hinder event-driven designs. Designing a framework - even one as simple as Simplevent -
can be time-consuming. This package provides an easy, small-scale solution for event-driven programming.
## Event Types
There are two types of `Event` in Simplevent: `StrEvent` and `RefEvent`. Both share a few similarities:
- Subscribers are encapsulated in a `list`, which is encapsulated by the `Event`.
- Subscribing the same object twice is not allowed by default (but this can be changed).
- Some sugar syntax is available: `+=` (subscribe), `-=` (unsubscribe), and `()` (invoke).
- Some magic method compatibility is available: `len` (currently the only one).
Each type can be customized/configured via their respective constructor. Refer to `docstrings` for more information.
### Str Event
An `StrEvent` is an `Event` that stores a "callback name" as a `string`. Once invoked, it will go through all of its
`subscribers`, looking for a method name that matches the stored `string`.
Here's an example where a video-game Character
is supposed to stop moving after a Timer has reached zero, with simplified code:
#### Example
```python
from simplevent import StrEvent
class Timer:
def __init__(self, init_time: float = 60):
"""
Initialized the timer.
:param init_time: The initial time, in seconds.
"""
self._time_left = init_time
self.time_is_up = StrEvent("on_time_is_up") # The event is defined here.
def start_timer(self):
"""Starts the timer."""
coroutine.start(self.decrease_time, loop_time=1, delay_time=0)
def stop_timer(self):
"""Stops the timer."""
coroutine.stop(self.decrease_time)
def decrease_time(self):
"""Decreases the time by 1."""
self._time_left -= 1
if self._time_left <= 0:
self.stop_timer()
self.time_is_up() # Sugar syntax; same as `self._time_is_up.invoke()`
class PlayerCharacter(ControllableGameObject):
def __init__(self):
self._is_input_enabled = True
GameMode.get_global_timer().time_is_up += self # Sugar syntax; same as `self._time_is_up.add(self)`
# Other code ...
# ...
def enable_input(self):
"""Enabled user input (e.g. movement, etc)."""
self._is_input_enabled = True
def disable_input(self):
"""Disables user input (e.g. movement, etc)."""
self._is_input_enabled = False
def on_time_is_up(self):
"""Called automatically when the global timer has reached zero."""
self.disable_input()
# Other code ...
# ...
```
### Ref Event
`Subscribers` of a `RefEvent` **must be `Callable` objects**. In other words, the `Subscriber` has to be a `function`, a `method`,
or a "functor-like" `object` (an `object` with the`__call__`magic method overloaded). That's because a `RefEvent` - unlike
an `StrEvent`- will call its `Subscribers` directly **instead** of looking for a `method` of a certain name.
Here's the same example as in `StrEvent` - a video-game Character that is supposed to stop moving after a Timer has reached
zero - but using `RefEvent` instead, again with simplified code:
#### Example
```python
from simplevent import RefEvent
class Timer:
def __init__(self, init_time: float = 60):
"""
Initialized the timer.
:param init_time: The initial time, in seconds.
"""
self._time_left = init_time
self.time_is_up = RefEvent() # The event is defined here.
def start_timer(self):
"""Starts the timer."""
coroutine.start(self.decrease_time, loop_time=1, delay_time=0)
def stop_timer(self):
"""Stops the timer."""
coroutine.stop(self.decrease_time)
def decrease_time(self):
"""Decreases the time by 1."""
self._time_left -= 1
if self._time_left <= 0:
self.stop_timer()
self.time_is_up() # Sugar syntax; same as `self._time_is_up.invoke()`
class PlayerCharacter(ControllableGameObject):
def __init__(self):
self._is_input_enabled = True
GameMode.get_global_timer().time_is_up += self.disable_input # Sugar syntax; same as `self._time_is_up.add(self.disable_input)`
# Other code ...
# ...
def enable_input(self):
"""Enabled user input (e.g. movement, etc)."""
self._is_input_enabled = True
def disable_input(self):
"""Disables user input (e.g. movement, etc)."""
self._is_input_enabled = False
# Other code ...
# ...
```
## Important Notes
### No Reference Management
Simplevent's `Event` instances do not automatically manage references to their `Subscribers`. That means it is up to the
developer to manage references. Here are a couple of examples:
#### Null Subscribers
- `o` (an `object`) becomes a `Subscriber` of `e` (an `Event`).
- `o` is destroyed via `del` before being unsubscribed from `e`.
The above is a problem because `e` will still attempt to call `o` when invoked, which will result in an `Error` (likely
a `TypeError`,`AttributeError`, or similar).
#### Persistent Subscribers
- `o` (an `object`) becomes a `Subscriber` of `e` (an `Event`).
- `o` is unreferenced everywhere in code, except in `e` (as a `subscriber`).
`o` exists inside `e` as a reference. Python's garbage collection will **not destroy `o`** until all references to it
cease to exist - **including the one inside `e`, which represents `o` as a `Subscriber`**. The developer must be very
careful and ensure that `o` is unsubscribed from `e` whenever needed.
Raw data
{
"_id": null,
"home_page": "https://github.com/matheusvilano/simplevent.git",
"name": "Simplevent",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.6",
"maintainer_email": "",
"keywords": "event observer listener subscription subscriber subject design pattern callback",
"author": "Matheus Vilano",
"author_email": "",
"download_url": "https://files.pythonhosted.org/packages/de/13/b6d5b25277ffead026d7e0d3b31fcd1f21d7813a92419fe6d25331702acb/Simplevent-1.0.0.tar.gz",
"platform": null,
"description": "# Simplevent\r\n\r\n## Summary\r\n\r\nSimplevent is a simple Event framework for Python, loosely based on the Observer design pattern. The package is minimal:\r\nit defines the `Event` base class and the `SignedEvent` and `NamedEvent` subclasses. An instance of either encapsulates\r\na `list` but **will also itself behave somewhat like a `list`**; this is essentially an _indirection_.\r\n\r\n## Observer Pattern\r\n\r\nSimplevent's tiny framework can be seen as a variation on the [Observer Pattern](https://en.wikipedia.org/wiki/Observer_pattern):\r\n\r\n- When you instantiate an `Event`, that instance's context (scope) is the `subject`.\r\n- When you subscribe an object to an `Event`, that object is an `observer`.\r\n- When you `invoke` an `Event` instance, you're notifying all `observers` that the `subject` has executed an important action.\r\n\r\n## Motivation\r\n\r\nSimplevent was a creation inspired by C# and its event framework that's already built into the language. The lack of a \r\nsimilar system in Python can hinder event-driven designs. Designing a framework - even one as simple as Simplevent - \r\ncan be time-consuming. This package provides an easy, small-scale solution for event-driven programming.\r\n\r\n## Event Types\r\n\r\nThere are two types of `Event` in Simplevent: `StrEvent` and `RefEvent`. Both share a few similarities:\r\n\r\n- Subscribers are encapsulated in a `list`, which is encapsulated by the `Event`.\r\n- Subscribing the same object twice is not allowed by default (but this can be changed).\r\n- Some sugar syntax is available: `+=` (subscribe), `-=` (unsubscribe), and `()` (invoke).\r\n- Some magic method compatibility is available: `len` (currently the only one).\r\n\r\nEach type can be customized/configured via their respective constructor. Refer to `docstrings` for more information.\r\n\r\n### Str Event\r\n\r\nAn `StrEvent` is an `Event` that stores a \"callback name\" as a `string`. Once invoked, it will go through all of its \r\n`subscribers`, looking for a method name that matches the stored `string`. \r\n\r\nHere's an example where a video-game Character \r\nis supposed to stop moving after a Timer has reached zero, with simplified code:\r\n\r\n#### Example\r\n```python\r\nfrom simplevent import StrEvent\r\n\r\nclass Timer:\r\n \r\n def __init__(self, init_time: float = 60):\r\n \"\"\"\r\n Initialized the timer.\r\n :param init_time: The initial time, in seconds.\r\n \"\"\"\r\n self._time_left = init_time\r\n self.time_is_up = StrEvent(\"on_time_is_up\") # The event is defined here.\r\n \r\n def start_timer(self):\r\n \"\"\"Starts the timer.\"\"\"\r\n coroutine.start(self.decrease_time, loop_time=1, delay_time=0)\r\n \r\n def stop_timer(self):\r\n \"\"\"Stops the timer.\"\"\"\r\n coroutine.stop(self.decrease_time)\r\n \r\n def decrease_time(self):\r\n \"\"\"Decreases the time by 1.\"\"\"\r\n self._time_left -= 1\r\n if self._time_left <= 0:\r\n self.stop_timer()\r\n self.time_is_up() # Sugar syntax; same as `self._time_is_up.invoke()`\r\n\r\nclass PlayerCharacter(ControllableGameObject):\r\n \r\n def __init__(self):\r\n self._is_input_enabled = True\r\n GameMode.get_global_timer().time_is_up += self # Sugar syntax; same as `self._time_is_up.add(self)`\r\n # Other code ...\r\n # ...\r\n \r\n def enable_input(self):\r\n \"\"\"Enabled user input (e.g. movement, etc).\"\"\"\r\n self._is_input_enabled = True\r\n\r\n def disable_input(self):\r\n \"\"\"Disables user input (e.g. movement, etc).\"\"\"\r\n self._is_input_enabled = False\r\n \r\n def on_time_is_up(self):\r\n \"\"\"Called automatically when the global timer has reached zero.\"\"\"\r\n self.disable_input()\r\n\r\n # Other code ...\r\n # ...\r\n```\r\n\r\n### Ref Event\r\n\r\n`Subscribers` of a `RefEvent` **must be `Callable` objects**. In other words, the `Subscriber` has to be a `function`, a `method`, \r\nor a \"functor-like\" `object` (an `object` with the`__call__`magic method overloaded). That's because a `RefEvent` - unlike \r\nan `StrEvent`- will call its `Subscribers` directly **instead** of looking for a `method` of a certain name.\r\n\r\nHere's the same example as in `StrEvent` - a video-game Character that is supposed to stop moving after a Timer has reached \r\nzero - but using `RefEvent` instead, again with simplified code:\r\n\r\n#### Example\r\n```python\r\nfrom simplevent import RefEvent\r\n\r\nclass Timer:\r\n \r\n def __init__(self, init_time: float = 60):\r\n \"\"\"\r\n Initialized the timer.\r\n :param init_time: The initial time, in seconds.\r\n \"\"\"\r\n self._time_left = init_time\r\n self.time_is_up = RefEvent() # The event is defined here.\r\n \r\n def start_timer(self):\r\n \"\"\"Starts the timer.\"\"\"\r\n coroutine.start(self.decrease_time, loop_time=1, delay_time=0)\r\n \r\n def stop_timer(self):\r\n \"\"\"Stops the timer.\"\"\"\r\n coroutine.stop(self.decrease_time)\r\n \r\n def decrease_time(self):\r\n \"\"\"Decreases the time by 1.\"\"\"\r\n self._time_left -= 1\r\n if self._time_left <= 0:\r\n self.stop_timer()\r\n self.time_is_up() # Sugar syntax; same as `self._time_is_up.invoke()`\r\n\r\nclass PlayerCharacter(ControllableGameObject):\r\n \r\n def __init__(self):\r\n self._is_input_enabled = True\r\n GameMode.get_global_timer().time_is_up += self.disable_input # Sugar syntax; same as `self._time_is_up.add(self.disable_input)`\r\n # Other code ...\r\n # ...\r\n \r\n def enable_input(self):\r\n \"\"\"Enabled user input (e.g. movement, etc).\"\"\"\r\n self._is_input_enabled = True\r\n\r\n def disable_input(self):\r\n \"\"\"Disables user input (e.g. movement, etc).\"\"\"\r\n self._is_input_enabled = False\r\n\r\n # Other code ...\r\n # ...\r\n```\r\n\r\n## Important Notes\r\n\r\n### No Reference Management\r\n\r\nSimplevent's `Event` instances do not automatically manage references to their `Subscribers`. That means it is up to the \r\ndeveloper to manage references. Here are a couple of examples:\r\n\r\n#### Null Subscribers\r\n- `o` (an `object`) becomes a `Subscriber` of `e` (an `Event`).\r\n- `o` is destroyed via `del` before being unsubscribed from `e`.\r\n\r\nThe above is a problem because `e` will still attempt to call `o` when invoked, which will result in an `Error` (likely \r\na `TypeError`,`AttributeError`, or similar).\r\n\r\n#### Persistent Subscribers\r\n- `o` (an `object`) becomes a `Subscriber` of `e` (an `Event`).\r\n- `o` is unreferenced everywhere in code, except in `e` (as a `subscriber`).\r\n\r\n`o` exists inside `e` as a reference. Python's garbage collection will **not destroy `o`** until all references to it \r\ncease to exist - **including the one inside `e`, which represents `o` as a `Subscriber`**. The developer must be very \r\ncareful and ensure that `o` is unsubscribed from `e` whenever needed.\r\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A simple framework for event-driven programming, based on the Observer design pattern.",
"version": "1.0.0",
"split_keywords": [
"event",
"observer",
"listener",
"subscription",
"subscriber",
"subject",
"design",
"pattern",
"callback"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "de13b6d5b25277ffead026d7e0d3b31fcd1f21d7813a92419fe6d25331702acb",
"md5": "dd82bfa0ee5c4de049e510fd5387cc94",
"sha256": "299dbae49399c3bafe2982b09ebe28d8af606a70480a5a0690163a66c0703884"
},
"downloads": -1,
"filename": "Simplevent-1.0.0.tar.gz",
"has_sig": false,
"md5_digest": "dd82bfa0ee5c4de049e510fd5387cc94",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.6",
"size": 5477,
"upload_time": "2023-03-12T18:56:56",
"upload_time_iso_8601": "2023-03-12T18:56:56.904921Z",
"url": "https://files.pythonhosted.org/packages/de/13/b6d5b25277ffead026d7e0d3b31fcd1f21d7813a92419fe6d25331702acb/Simplevent-1.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-03-12 18:56:56",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "matheusvilano",
"github_project": "simplevent.git",
"lcname": "simplevent"
}