Simplevent


NameSimplevent JSON
Version 1.0.0 PyPI version JSON
download
home_pagehttps://github.com/matheusvilano/simplevent.git
SummaryA simple framework for event-driven programming, based on the Observer design pattern.
upload_time2023-03-12 18:56:56
maintainer
docs_urlNone
authorMatheus Vilano
requires_python>=3.6
licenseMIT
keywords event observer listener subscription subscriber subject design pattern callback
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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"
}
        
Elapsed time: 0.04248s