delegate-events 0.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- delegate_events-0.0.0/LICENSE +21 -0
- delegate_events-0.0.0/PKG-INFO +155 -0
- delegate_events-0.0.0/README.md +122 -0
- delegate_events-0.0.0/pyproject.toml +48 -0
- delegate_events-0.0.0/setup.cfg +4 -0
- delegate_events-0.0.0/src/delegate/events/__init__.py +13 -0
- delegate_events-0.0.0/src/delegate/events/core/channel.py +118 -0
- delegate_events-0.0.0/src/delegate/events/core/channel_closed_error.py +5 -0
- delegate_events-0.0.0/src/delegate/events/core/event.py +7 -0
- delegate_events-0.0.0/src/delegate/events/core/handler_already_subscribed_error.py +6 -0
- delegate_events-0.0.0/src/delegate/events/core/handler_not_subscribed_error.py +6 -0
- delegate_events-0.0.0/src/delegate/events/core/queued_event.py +18 -0
- delegate_events-0.0.0/src/delegate_events.egg-info/PKG-INFO +155 -0
- delegate_events-0.0.0/src/delegate_events.egg-info/SOURCES.txt +16 -0
- delegate_events-0.0.0/src/delegate_events.egg-info/dependency_links.txt +1 -0
- delegate_events-0.0.0/src/delegate_events.egg-info/requires.txt +5 -0
- delegate_events-0.0.0/src/delegate_events.egg-info/top_level.txt +1 -0
- delegate_events-0.0.0/tests/test_channel.py +304 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Dagrofa BI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: delegate-events
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Python implementation of the Event Pattern
|
|
5
|
+
Author-email: Anders Madsen <anders.madsen@alphavue.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: repository, https://github.com/apmadsen/delegate-events
|
|
8
|
+
Keywords: windows,linux,delegates,events
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Development Status :: 6 - Mature
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: delegate-pattern==0.0.0
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest>=8.3; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-cov>=6.1; extra == "test"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
[](https://github.com/apmadsen/delegate-events/actions/workflows/python-test.yml)
|
|
35
|
+
[](https://github.com/apmadsen/delegate-events/actions/workflows/python-test-coverage.yml)
|
|
36
|
+
[](https://github.com/apmadsen/delegate-events/releases)
|
|
37
|
+

|
|
38
|
+

|
|
39
|
+
[](https://pepy.tech/projects/delegate-events)
|
|
40
|
+
|
|
41
|
+
# delegate-events: Python implementation of the Event Pattern.
|
|
42
|
+
|
|
43
|
+
delegate-events provides a basic implementation of the well-known Event or Pub/Sub Pattern, and is built on top of the `delegate-pattern` package which handles the delegation part.
|
|
44
|
+
|
|
45
|
+
## Example
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from delegate.pattern import delegate
|
|
49
|
+
from delegate.events import Channel, Event
|
|
50
|
+
|
|
51
|
+
class OnSomethingEvent(Event):
|
|
52
|
+
def __init__(self, what: str, why: str):
|
|
53
|
+
self.what = what
|
|
54
|
+
self.why = why
|
|
55
|
+
|
|
56
|
+
def __str__(self):
|
|
57
|
+
return f"{self.what} happened because of {self.why}"
|
|
58
|
+
|
|
59
|
+
class Class1:
|
|
60
|
+
on_something = delegate(Channel[OnSomethingEvent])
|
|
61
|
+
|
|
62
|
+
inst = Class1()
|
|
63
|
+
msg_queue: list[Event] = []
|
|
64
|
+
|
|
65
|
+
def fn(publisher, event: OnSomethingEvent):
|
|
66
|
+
msg_queue.append(event)
|
|
67
|
+
|
|
68
|
+
inst.on_something.subscribe(fn)
|
|
69
|
+
inst.on_something.fire(OnSomethingEvent("Something", "some other thing"))
|
|
70
|
+
|
|
71
|
+
str(msg_queue[0]) # => "Something happened because of some other thing"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
# API
|
|
75
|
+
|
|
76
|
+
## Event class
|
|
77
|
+
The `Event` class is the base class of all events. Events are the classes holding the event information, when an event is fired, thus, when subclassed, should contain properties/fields of the event information.
|
|
78
|
+
|
|
79
|
+
#### Example
|
|
80
|
+
```python
|
|
81
|
+
from delegate.events import Event
|
|
82
|
+
|
|
83
|
+
class OnSomething(Event):
|
|
84
|
+
def __init__(self, what: str, why: str):
|
|
85
|
+
self.what = what
|
|
86
|
+
self.why = why
|
|
87
|
+
|
|
88
|
+
def __str__(self):
|
|
89
|
+
return f"{self.what} happened because of {self.why}"
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Channel class
|
|
94
|
+
The `Channel` class is the event channel returned by the delegate, and its jobs are handling subscriptions and firing of events.
|
|
95
|
+
|
|
96
|
+
### Channel.subscribe(handler: _Callable[[object, T\<Event>], None]_)
|
|
97
|
+
|
|
98
|
+
The `subscribe` function subscribes a handler function to the channel. The function takes 1 argument:
|
|
99
|
+
- handler: `Callable[[object, Event], None]`
|
|
100
|
+
|
|
101
|
+
#### Example
|
|
102
|
+
```python
|
|
103
|
+
from delegate.events import Channel
|
|
104
|
+
|
|
105
|
+
channel = Channel[OnSomethingEvent]()
|
|
106
|
+
|
|
107
|
+
def handler(sender: object, args: OnSomethingEvent) -> None:
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
channel.subscribe(handler)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
### Channel.unsubscribe(handler: _Callable[[object, T\<Event>], None]_)
|
|
115
|
+
The `unsubscribe` function unsubscribes a handler function from the channel. The function takes 1 argument:
|
|
116
|
+
- handler: `Callable[[object, Event], None]`
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from delegate.events import Channel
|
|
120
|
+
|
|
121
|
+
channel = Channel[OnSomethingEvent]()
|
|
122
|
+
|
|
123
|
+
def handler(sender: object, args: OnSomethingEvent) -> None:
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
channel.subscribe(handler)
|
|
127
|
+
channel.unsubscribe(handler)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Channel.fire(event: _T\<Event>_, *, ttl: _float | None = 0_) -> _bool_ function
|
|
131
|
+
The `fire` function fires the event, invoking all subscribed handlers. The function returns True if event was successfully sent and takes 1 argument:
|
|
132
|
+
- event: `Event`
|
|
133
|
+
|
|
134
|
+
and 1 optional keyword argument:
|
|
135
|
+
- ttl: `float | None` the time-to-live (seconds) of the event.
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from delegate.events import Channel
|
|
139
|
+
|
|
140
|
+
channel = Channel[OnSomethingEvent]()
|
|
141
|
+
|
|
142
|
+
msg_queue: list[Event] = []
|
|
143
|
+
|
|
144
|
+
def handler(sender: object, event: OnSomethingEvent) -> None:
|
|
145
|
+
msg_queue.append(event)
|
|
146
|
+
|
|
147
|
+
channel.fire(OnSomethingEvent("Something", "some other thing")) # => False
|
|
148
|
+
channel.fire(OnSomethingEvent("Something", "some other thing"), ttl=10) # => False - event stays in queue and will be sent if subcrribed to within 10 seconds though
|
|
149
|
+
|
|
150
|
+
channel.subscribe(handler)
|
|
151
|
+
|
|
152
|
+
channel.fire(OnSomethingEvent("Something", "some other thing")) # => True
|
|
153
|
+
|
|
154
|
+
len(msg_queue) # => 2
|
|
155
|
+
```
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
[](https://github.com/apmadsen/delegate-events/actions/workflows/python-test.yml)
|
|
2
|
+
[](https://github.com/apmadsen/delegate-events/actions/workflows/python-test-coverage.yml)
|
|
3
|
+
[](https://github.com/apmadsen/delegate-events/releases)
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
[](https://pepy.tech/projects/delegate-events)
|
|
7
|
+
|
|
8
|
+
# delegate-events: Python implementation of the Event Pattern.
|
|
9
|
+
|
|
10
|
+
delegate-events provides a basic implementation of the well-known Event or Pub/Sub Pattern, and is built on top of the `delegate-pattern` package which handles the delegation part.
|
|
11
|
+
|
|
12
|
+
## Example
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from delegate.pattern import delegate
|
|
16
|
+
from delegate.events import Channel, Event
|
|
17
|
+
|
|
18
|
+
class OnSomethingEvent(Event):
|
|
19
|
+
def __init__(self, what: str, why: str):
|
|
20
|
+
self.what = what
|
|
21
|
+
self.why = why
|
|
22
|
+
|
|
23
|
+
def __str__(self):
|
|
24
|
+
return f"{self.what} happened because of {self.why}"
|
|
25
|
+
|
|
26
|
+
class Class1:
|
|
27
|
+
on_something = delegate(Channel[OnSomethingEvent])
|
|
28
|
+
|
|
29
|
+
inst = Class1()
|
|
30
|
+
msg_queue: list[Event] = []
|
|
31
|
+
|
|
32
|
+
def fn(publisher, event: OnSomethingEvent):
|
|
33
|
+
msg_queue.append(event)
|
|
34
|
+
|
|
35
|
+
inst.on_something.subscribe(fn)
|
|
36
|
+
inst.on_something.fire(OnSomethingEvent("Something", "some other thing"))
|
|
37
|
+
|
|
38
|
+
str(msg_queue[0]) # => "Something happened because of some other thing"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
# API
|
|
42
|
+
|
|
43
|
+
## Event class
|
|
44
|
+
The `Event` class is the base class of all events. Events are the classes holding the event information, when an event is fired, thus, when subclassed, should contain properties/fields of the event information.
|
|
45
|
+
|
|
46
|
+
#### Example
|
|
47
|
+
```python
|
|
48
|
+
from delegate.events import Event
|
|
49
|
+
|
|
50
|
+
class OnSomething(Event):
|
|
51
|
+
def __init__(self, what: str, why: str):
|
|
52
|
+
self.what = what
|
|
53
|
+
self.why = why
|
|
54
|
+
|
|
55
|
+
def __str__(self):
|
|
56
|
+
return f"{self.what} happened because of {self.why}"
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Channel class
|
|
61
|
+
The `Channel` class is the event channel returned by the delegate, and its jobs are handling subscriptions and firing of events.
|
|
62
|
+
|
|
63
|
+
### Channel.subscribe(handler: _Callable[[object, T\<Event>], None]_)
|
|
64
|
+
|
|
65
|
+
The `subscribe` function subscribes a handler function to the channel. The function takes 1 argument:
|
|
66
|
+
- handler: `Callable[[object, Event], None]`
|
|
67
|
+
|
|
68
|
+
#### Example
|
|
69
|
+
```python
|
|
70
|
+
from delegate.events import Channel
|
|
71
|
+
|
|
72
|
+
channel = Channel[OnSomethingEvent]()
|
|
73
|
+
|
|
74
|
+
def handler(sender: object, args: OnSomethingEvent) -> None:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
channel.subscribe(handler)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
### Channel.unsubscribe(handler: _Callable[[object, T\<Event>], None]_)
|
|
82
|
+
The `unsubscribe` function unsubscribes a handler function from the channel. The function takes 1 argument:
|
|
83
|
+
- handler: `Callable[[object, Event], None]`
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from delegate.events import Channel
|
|
87
|
+
|
|
88
|
+
channel = Channel[OnSomethingEvent]()
|
|
89
|
+
|
|
90
|
+
def handler(sender: object, args: OnSomethingEvent) -> None:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
channel.subscribe(handler)
|
|
94
|
+
channel.unsubscribe(handler)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Channel.fire(event: _T\<Event>_, *, ttl: _float | None = 0_) -> _bool_ function
|
|
98
|
+
The `fire` function fires the event, invoking all subscribed handlers. The function returns True if event was successfully sent and takes 1 argument:
|
|
99
|
+
- event: `Event`
|
|
100
|
+
|
|
101
|
+
and 1 optional keyword argument:
|
|
102
|
+
- ttl: `float | None` the time-to-live (seconds) of the event.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from delegate.events import Channel
|
|
106
|
+
|
|
107
|
+
channel = Channel[OnSomethingEvent]()
|
|
108
|
+
|
|
109
|
+
msg_queue: list[Event] = []
|
|
110
|
+
|
|
111
|
+
def handler(sender: object, event: OnSomethingEvent) -> None:
|
|
112
|
+
msg_queue.append(event)
|
|
113
|
+
|
|
114
|
+
channel.fire(OnSomethingEvent("Something", "some other thing")) # => False
|
|
115
|
+
channel.fire(OnSomethingEvent("Something", "some other thing"), ttl=10) # => False - event stays in queue and will be sent if subcrribed to within 10 seconds though
|
|
116
|
+
|
|
117
|
+
channel.subscribe(handler)
|
|
118
|
+
|
|
119
|
+
channel.fire(OnSomethingEvent("Something", "some other thing")) # => True
|
|
120
|
+
|
|
121
|
+
len(msg_queue) # => 2
|
|
122
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "delegate-events"
|
|
3
|
+
version = "0.0.0"
|
|
4
|
+
description = "Python implementation of the Event Pattern"
|
|
5
|
+
keywords = ["windows", "linux", "delegates", "events"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Anders Madsen", email = "anders.madsen@alphavue.com" }
|
|
9
|
+
]
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 5 - Production/Stable",
|
|
13
|
+
"Development Status :: 6 - Mature",
|
|
14
|
+
"Operating System :: Microsoft :: Windows",
|
|
15
|
+
"Operating System :: POSIX :: Linux",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Natural Language :: English",
|
|
18
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
"Topic :: Software Development :: Libraries",
|
|
27
|
+
"Typing :: Typed"
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"delegate-pattern==0.0.0"
|
|
31
|
+
]
|
|
32
|
+
requires-python = ">=3.10"
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
repository = "https://github.com/apmadsen/delegate-events"
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
test = [
|
|
39
|
+
"pytest>=8.3",
|
|
40
|
+
"pytest-cov>=6.1",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.setuptools-git-versioning]
|
|
44
|
+
enabled = true
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
requires = ["setuptools >= 77.0.3", "setuptools-git-versioning >= 2.1.0"]
|
|
48
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from delegate.events.core.channel_closed_error import ChannelClosedError
|
|
2
|
+
from delegate.events.core.channel import Channel
|
|
3
|
+
from delegate.events.core.event import Event
|
|
4
|
+
from delegate.events.core.handler_already_subscribed_error import HandlerAlreadySubscribedError
|
|
5
|
+
from delegate.events.core.handler_not_subscribed_error import HandlerNotSubscribedError
|
|
6
|
+
|
|
7
|
+
__all__ = (
|
|
8
|
+
'Channel',
|
|
9
|
+
'Event',
|
|
10
|
+
'ChannelClosedError',
|
|
11
|
+
'HandlerAlreadySubscribedError',
|
|
12
|
+
'HandlerNotSubscribedError',
|
|
13
|
+
)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Generic, TypeVar, MutableSequence, Callable, cast, overload
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
from threading import RLock
|
|
5
|
+
from weakref import ref
|
|
6
|
+
|
|
7
|
+
from delegate.events.core.channel_closed_error import ChannelClosedError
|
|
8
|
+
from delegate.events.core.event import Event
|
|
9
|
+
from delegate.events.core.queued_event import QueuedEvent
|
|
10
|
+
from delegate.events.core.handler_already_subscribed_error import HandlerAlreadySubscribedError
|
|
11
|
+
from delegate.events.core.handler_not_subscribed_error import HandlerNotSubscribedError
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T", bound=Event, contravariant=True)
|
|
14
|
+
class Channel(Generic[T]):
|
|
15
|
+
__slots__ = [ "__publisher", "__subscribers", "__queue", "__queue_lock", "__weakref__", "__closed" ]
|
|
16
|
+
|
|
17
|
+
@overload
|
|
18
|
+
def __init__(self) -> None:
|
|
19
|
+
"""
|
|
20
|
+
Creates a channel without a publisher. This is meant for testing purposes.
|
|
21
|
+
"""
|
|
22
|
+
...
|
|
23
|
+
@overload
|
|
24
|
+
def __init__(self, delegator: object) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Creates a delegate channel for specific publisher (delegator).
|
|
27
|
+
"""
|
|
28
|
+
...
|
|
29
|
+
def __init__(self, delegator: object | None = None):
|
|
30
|
+
self.__publisher = ref(delegator) if delegator is not None else None
|
|
31
|
+
self.__subscribers: MutableSequence[Callable[[object, T], None]] = []
|
|
32
|
+
self.__queue: MutableSequence[QueuedEvent] = []
|
|
33
|
+
self.__queue_lock = RLock()
|
|
34
|
+
self.__closed = False
|
|
35
|
+
|
|
36
|
+
def subscribe(self, handler: Callable[[object, T], None]) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Subscribes to the event channel.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
handler (Callable[[object, T], None]): The event handler
|
|
42
|
+
"""
|
|
43
|
+
if self.__closed:
|
|
44
|
+
raise ChannelClosedError
|
|
45
|
+
|
|
46
|
+
if handler in self.__subscribers:
|
|
47
|
+
raise HandlerAlreadySubscribedError
|
|
48
|
+
|
|
49
|
+
self.__subscribers.append(handler)
|
|
50
|
+
|
|
51
|
+
with self.__queue_lock:
|
|
52
|
+
for event in self.__queue:
|
|
53
|
+
if not event.expired:
|
|
54
|
+
self.fire(cast(T, event.event))
|
|
55
|
+
self.__queue.clear()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def unsubscribe(self, handler: Callable[[object, T], None]) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Unsubscribes handler from event channel.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
handler (Callable[[object, T], None]): The event handler
|
|
64
|
+
"""
|
|
65
|
+
if self.__closed:
|
|
66
|
+
raise ChannelClosedError
|
|
67
|
+
|
|
68
|
+
if handler not in self.__subscribers:
|
|
69
|
+
raise HandlerNotSubscribedError
|
|
70
|
+
|
|
71
|
+
self.__subscribers.remove(handler)
|
|
72
|
+
|
|
73
|
+
def fire(self, event: T, *, ttl: float | None = 0) -> bool:
|
|
74
|
+
"""
|
|
75
|
+
Fires the event, invoking all subscribed handlers
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
event (Event): The event
|
|
79
|
+
ttl (float, optional): The time (seconds) the event may live in the queue while awaiting subcriptions
|
|
80
|
+
Returns:
|
|
81
|
+
bool: Returns True when event was successfully sent to one or more subscribers.
|
|
82
|
+
"""
|
|
83
|
+
if self.__closed:
|
|
84
|
+
raise ChannelClosedError
|
|
85
|
+
|
|
86
|
+
if not self.__subscribers:
|
|
87
|
+
if ttl:
|
|
88
|
+
with self.__queue_lock:
|
|
89
|
+
self.__queue.append(QueuedEvent(event, datetime.now() + timedelta(seconds = ttl)))
|
|
90
|
+
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
for subscriber in self.__subscribers:
|
|
94
|
+
subscriber(self.__publisher() if self.__publisher else None, event)
|
|
95
|
+
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
def close(self) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Closes the channel.
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
ChannelClosedError: _description_
|
|
104
|
+
"""
|
|
105
|
+
if self.__closed:
|
|
106
|
+
raise ChannelClosedError
|
|
107
|
+
|
|
108
|
+
self.__closed = True
|
|
109
|
+
self.__subscribers.clear()
|
|
110
|
+
self.__queue.clear()
|
|
111
|
+
|
|
112
|
+
def __iadd__(self, handler: Callable[[object, T], None]) -> Channel[T]:
|
|
113
|
+
self.subscribe(handler)
|
|
114
|
+
return self
|
|
115
|
+
|
|
116
|
+
def __isub__(self, handler: Callable[[object, T], None]) -> Channel[T]:
|
|
117
|
+
self.unsubscribe(handler)
|
|
118
|
+
return self
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from delegate.events.core.event import Event
|
|
4
|
+
|
|
5
|
+
class QueuedEvent:
|
|
6
|
+
__slots__ = [ "__event", "__expires" ]
|
|
7
|
+
|
|
8
|
+
def __init__(self, event: Event, expires: datetime) -> None:
|
|
9
|
+
self.__event = event
|
|
10
|
+
self.__expires = expires
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def event(self) -> Event:
|
|
14
|
+
return self.__event
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def expired(self) -> bool:
|
|
18
|
+
return self.__expires < datetime.now()
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: delegate-events
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Python implementation of the Event Pattern
|
|
5
|
+
Author-email: Anders Madsen <anders.madsen@alphavue.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: repository, https://github.com/apmadsen/delegate-events
|
|
8
|
+
Keywords: windows,linux,delegates,events
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Development Status :: 6 - Mature
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: delegate-pattern==0.0.0
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest>=8.3; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-cov>=6.1; extra == "test"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
[](https://github.com/apmadsen/delegate-events/actions/workflows/python-test.yml)
|
|
35
|
+
[](https://github.com/apmadsen/delegate-events/actions/workflows/python-test-coverage.yml)
|
|
36
|
+
[](https://github.com/apmadsen/delegate-events/releases)
|
|
37
|
+

|
|
38
|
+

|
|
39
|
+
[](https://pepy.tech/projects/delegate-events)
|
|
40
|
+
|
|
41
|
+
# delegate-events: Python implementation of the Event Pattern.
|
|
42
|
+
|
|
43
|
+
delegate-events provides a basic implementation of the well-known Event or Pub/Sub Pattern, and is built on top of the `delegate-pattern` package which handles the delegation part.
|
|
44
|
+
|
|
45
|
+
## Example
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from delegate.pattern import delegate
|
|
49
|
+
from delegate.events import Channel, Event
|
|
50
|
+
|
|
51
|
+
class OnSomethingEvent(Event):
|
|
52
|
+
def __init__(self, what: str, why: str):
|
|
53
|
+
self.what = what
|
|
54
|
+
self.why = why
|
|
55
|
+
|
|
56
|
+
def __str__(self):
|
|
57
|
+
return f"{self.what} happened because of {self.why}"
|
|
58
|
+
|
|
59
|
+
class Class1:
|
|
60
|
+
on_something = delegate(Channel[OnSomethingEvent])
|
|
61
|
+
|
|
62
|
+
inst = Class1()
|
|
63
|
+
msg_queue: list[Event] = []
|
|
64
|
+
|
|
65
|
+
def fn(publisher, event: OnSomethingEvent):
|
|
66
|
+
msg_queue.append(event)
|
|
67
|
+
|
|
68
|
+
inst.on_something.subscribe(fn)
|
|
69
|
+
inst.on_something.fire(OnSomethingEvent("Something", "some other thing"))
|
|
70
|
+
|
|
71
|
+
str(msg_queue[0]) # => "Something happened because of some other thing"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
# API
|
|
75
|
+
|
|
76
|
+
## Event class
|
|
77
|
+
The `Event` class is the base class of all events. Events are the classes holding the event information, when an event is fired, thus, when subclassed, should contain properties/fields of the event information.
|
|
78
|
+
|
|
79
|
+
#### Example
|
|
80
|
+
```python
|
|
81
|
+
from delegate.events import Event
|
|
82
|
+
|
|
83
|
+
class OnSomething(Event):
|
|
84
|
+
def __init__(self, what: str, why: str):
|
|
85
|
+
self.what = what
|
|
86
|
+
self.why = why
|
|
87
|
+
|
|
88
|
+
def __str__(self):
|
|
89
|
+
return f"{self.what} happened because of {self.why}"
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Channel class
|
|
94
|
+
The `Channel` class is the event channel returned by the delegate, and its jobs are handling subscriptions and firing of events.
|
|
95
|
+
|
|
96
|
+
### Channel.subscribe(handler: _Callable[[object, T\<Event>], None]_)
|
|
97
|
+
|
|
98
|
+
The `subscribe` function subscribes a handler function to the channel. The function takes 1 argument:
|
|
99
|
+
- handler: `Callable[[object, Event], None]`
|
|
100
|
+
|
|
101
|
+
#### Example
|
|
102
|
+
```python
|
|
103
|
+
from delegate.events import Channel
|
|
104
|
+
|
|
105
|
+
channel = Channel[OnSomethingEvent]()
|
|
106
|
+
|
|
107
|
+
def handler(sender: object, args: OnSomethingEvent) -> None:
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
channel.subscribe(handler)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
### Channel.unsubscribe(handler: _Callable[[object, T\<Event>], None]_)
|
|
115
|
+
The `unsubscribe` function unsubscribes a handler function from the channel. The function takes 1 argument:
|
|
116
|
+
- handler: `Callable[[object, Event], None]`
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from delegate.events import Channel
|
|
120
|
+
|
|
121
|
+
channel = Channel[OnSomethingEvent]()
|
|
122
|
+
|
|
123
|
+
def handler(sender: object, args: OnSomethingEvent) -> None:
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
channel.subscribe(handler)
|
|
127
|
+
channel.unsubscribe(handler)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Channel.fire(event: _T\<Event>_, *, ttl: _float | None = 0_) -> _bool_ function
|
|
131
|
+
The `fire` function fires the event, invoking all subscribed handlers. The function returns True if event was successfully sent and takes 1 argument:
|
|
132
|
+
- event: `Event`
|
|
133
|
+
|
|
134
|
+
and 1 optional keyword argument:
|
|
135
|
+
- ttl: `float | None` the time-to-live (seconds) of the event.
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from delegate.events import Channel
|
|
139
|
+
|
|
140
|
+
channel = Channel[OnSomethingEvent]()
|
|
141
|
+
|
|
142
|
+
msg_queue: list[Event] = []
|
|
143
|
+
|
|
144
|
+
def handler(sender: object, event: OnSomethingEvent) -> None:
|
|
145
|
+
msg_queue.append(event)
|
|
146
|
+
|
|
147
|
+
channel.fire(OnSomethingEvent("Something", "some other thing")) # => False
|
|
148
|
+
channel.fire(OnSomethingEvent("Something", "some other thing"), ttl=10) # => False - event stays in queue and will be sent if subcrribed to within 10 seconds though
|
|
149
|
+
|
|
150
|
+
channel.subscribe(handler)
|
|
151
|
+
|
|
152
|
+
channel.fire(OnSomethingEvent("Something", "some other thing")) # => True
|
|
153
|
+
|
|
154
|
+
len(msg_queue) # => 2
|
|
155
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/delegate/events/__init__.py
|
|
5
|
+
src/delegate/events/core/channel.py
|
|
6
|
+
src/delegate/events/core/channel_closed_error.py
|
|
7
|
+
src/delegate/events/core/event.py
|
|
8
|
+
src/delegate/events/core/handler_already_subscribed_error.py
|
|
9
|
+
src/delegate/events/core/handler_not_subscribed_error.py
|
|
10
|
+
src/delegate/events/core/queued_event.py
|
|
11
|
+
src/delegate_events.egg-info/PKG-INFO
|
|
12
|
+
src/delegate_events.egg-info/SOURCES.txt
|
|
13
|
+
src/delegate_events.egg-info/dependency_links.txt
|
|
14
|
+
src/delegate_events.egg-info/requires.txt
|
|
15
|
+
src/delegate_events.egg-info/top_level.txt
|
|
16
|
+
tests/test_channel.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
delegate
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# pyright: basic
|
|
2
|
+
from typing import Any
|
|
3
|
+
from time import sleep
|
|
4
|
+
import gc
|
|
5
|
+
from weakref import ref
|
|
6
|
+
from pytest import raises as assert_raises, fixture
|
|
7
|
+
|
|
8
|
+
from delegate.pattern import delegate
|
|
9
|
+
from delegate.events import Event, Channel, HandlerNotSubscribedError, HandlerAlreadySubscribedError, ChannelClosedError
|
|
10
|
+
|
|
11
|
+
class Event1(Event):
|
|
12
|
+
def __init__(self, arg1: str, arg2: int):
|
|
13
|
+
self.arg1 = arg1
|
|
14
|
+
self.arg2 = arg2
|
|
15
|
+
|
|
16
|
+
class Event2(Event):
|
|
17
|
+
def __init__(self, arg1: str, arg2: int):
|
|
18
|
+
self.arg1 = arg1
|
|
19
|
+
self.arg2 = arg2
|
|
20
|
+
|
|
21
|
+
class OnSomethingEvent(Event):
|
|
22
|
+
def __init__(self, what: str, why: str):
|
|
23
|
+
self.what = what
|
|
24
|
+
self.why = why
|
|
25
|
+
|
|
26
|
+
def __str__(self):
|
|
27
|
+
return f"{self.what} happened because of {self.why}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_channel_basics():
|
|
31
|
+
class Class1:
|
|
32
|
+
event1 = delegate(Channel[Event1])
|
|
33
|
+
|
|
34
|
+
inst = Class1()
|
|
35
|
+
msg_queue: list[tuple[object, Event]] = []
|
|
36
|
+
|
|
37
|
+
def fn(publisher, event: Event1):
|
|
38
|
+
msg_queue.append((publisher, event))
|
|
39
|
+
|
|
40
|
+
inst.event1.subscribe(fn)
|
|
41
|
+
inst.event1.fire(Event1("msg",1))
|
|
42
|
+
|
|
43
|
+
assert len(msg_queue) == 1
|
|
44
|
+
assert msg_queue[0][0] is inst
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_channel_closing():
|
|
48
|
+
class Class1:
|
|
49
|
+
event1 = delegate(Channel[Event1])
|
|
50
|
+
|
|
51
|
+
inst = Class1()
|
|
52
|
+
|
|
53
|
+
def fn(publisher, event: Event1):
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
inst.event1.close()
|
|
57
|
+
|
|
58
|
+
with assert_raises(ChannelClosedError):
|
|
59
|
+
inst.event1.close()
|
|
60
|
+
|
|
61
|
+
with assert_raises(ChannelClosedError):
|
|
62
|
+
inst.event1.subscribe(fn)
|
|
63
|
+
|
|
64
|
+
with assert_raises(ChannelClosedError):
|
|
65
|
+
inst.event1.unsubscribe(fn)
|
|
66
|
+
|
|
67
|
+
with assert_raises(ChannelClosedError):
|
|
68
|
+
inst.event1.fire(Event1("msg",2))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_garbage_collection():
|
|
72
|
+
class Event3(Event):
|
|
73
|
+
def __init__(self, arg1: str, arg2: int):
|
|
74
|
+
self.arg1 = arg1
|
|
75
|
+
self.arg2 = arg2
|
|
76
|
+
|
|
77
|
+
class Class1:
|
|
78
|
+
event3 = delegate(Channel[Event3])
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
inst = Class1()
|
|
83
|
+
channel = ref(inst.event3)
|
|
84
|
+
refs_before_gc = len(gc.get_referrers(channel()))
|
|
85
|
+
|
|
86
|
+
inst = None
|
|
87
|
+
del Class1
|
|
88
|
+
del Event3
|
|
89
|
+
|
|
90
|
+
gc.collect()
|
|
91
|
+
gc.collect()
|
|
92
|
+
gc.collect()
|
|
93
|
+
|
|
94
|
+
assert refs_before_gc > 0
|
|
95
|
+
assert channel() is None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_multiple_channels():
|
|
99
|
+
|
|
100
|
+
class Class1:
|
|
101
|
+
event1 = delegate(Channel[Event1])
|
|
102
|
+
event2 = delegate(Channel[Event2])
|
|
103
|
+
|
|
104
|
+
inst = Class1()
|
|
105
|
+
msg_queue: list[tuple[object, Event]] = []
|
|
106
|
+
|
|
107
|
+
def fn(publisher, event: Event):
|
|
108
|
+
msg_queue.append((publisher, event))
|
|
109
|
+
|
|
110
|
+
inst.event1.subscribe(fn)
|
|
111
|
+
inst.event2.subscribe(fn)
|
|
112
|
+
|
|
113
|
+
inst.event1.fire(Event1("msg",1))
|
|
114
|
+
inst.event2.fire(Event2("msg",2))
|
|
115
|
+
|
|
116
|
+
assert len(msg_queue) == 2
|
|
117
|
+
assert msg_queue[0][0] is inst
|
|
118
|
+
assert isinstance(msg_queue[0][1], Event1)
|
|
119
|
+
assert msg_queue[1][0] is inst
|
|
120
|
+
assert isinstance(msg_queue[1][1], Event2)
|
|
121
|
+
x=0
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_subscriptions():
|
|
126
|
+
|
|
127
|
+
class Class1:
|
|
128
|
+
event1 = delegate(Channel[Event1])
|
|
129
|
+
|
|
130
|
+
inst = Class1()
|
|
131
|
+
msg_queue: list[tuple[object, Event1]] = []
|
|
132
|
+
|
|
133
|
+
def fn1(publisher, event: Event1):
|
|
134
|
+
msg_queue.append((publisher, event))
|
|
135
|
+
|
|
136
|
+
def fn2(publisher, event: Event1):
|
|
137
|
+
msg_queue.append((publisher, event))
|
|
138
|
+
|
|
139
|
+
inst.event1.subscribe(fn1)
|
|
140
|
+
|
|
141
|
+
with assert_raises(HandlerAlreadySubscribedError):
|
|
142
|
+
inst.event1.subscribe(fn1)
|
|
143
|
+
|
|
144
|
+
inst.event1.unsubscribe(fn1)
|
|
145
|
+
|
|
146
|
+
with assert_raises(HandlerNotSubscribedError):
|
|
147
|
+
inst.event1.unsubscribe(fn1)
|
|
148
|
+
|
|
149
|
+
inst.event1 += fn1
|
|
150
|
+
inst.event1 += fn2
|
|
151
|
+
|
|
152
|
+
inst.event1.fire(Event1("msg",1))
|
|
153
|
+
|
|
154
|
+
assert len(msg_queue) == 2
|
|
155
|
+
assert msg_queue[0][0] is inst
|
|
156
|
+
assert msg_queue[1][0] is inst
|
|
157
|
+
assert msg_queue[0][1] is msg_queue[1][1] # events should be same instance
|
|
158
|
+
|
|
159
|
+
inst.event1 -= fn2
|
|
160
|
+
inst.event1.fire(Event1("msg",2))
|
|
161
|
+
|
|
162
|
+
assert len(msg_queue) == 3
|
|
163
|
+
assert msg_queue[2][1].arg2 ==2
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_event_queue():
|
|
167
|
+
|
|
168
|
+
class Class1:
|
|
169
|
+
event1 = delegate(Channel[Event1])
|
|
170
|
+
|
|
171
|
+
inst = Class1()
|
|
172
|
+
msg_queue: list[tuple[object, Event1]] = []
|
|
173
|
+
|
|
174
|
+
def fn(publisher, event: Event1):
|
|
175
|
+
msg_queue.append((publisher, event))
|
|
176
|
+
|
|
177
|
+
inst.event1.fire(Event1("msg",1)) # this msg will not be received because it has no ttl
|
|
178
|
+
inst.event1.fire(Event1("msg",2), ttl=60)
|
|
179
|
+
inst.event1.subscribe(fn)
|
|
180
|
+
|
|
181
|
+
assert len(msg_queue) == 1
|
|
182
|
+
assert msg_queue[0][0] is inst
|
|
183
|
+
|
|
184
|
+
inst = Class1()
|
|
185
|
+
msg_queue.clear()
|
|
186
|
+
|
|
187
|
+
inst.event1.fire(Event1("msg",1), ttl=0.001) # this msg will not be received because it has no ttl
|
|
188
|
+
sleep(0.01)
|
|
189
|
+
inst.event1.subscribe(fn)
|
|
190
|
+
|
|
191
|
+
assert len(msg_queue) == 0
|
|
192
|
+
|
|
193
|
+
def test_multiple_instances_of_same_class():
|
|
194
|
+
|
|
195
|
+
class Class1:
|
|
196
|
+
event1 = delegate(Channel[Event1])
|
|
197
|
+
|
|
198
|
+
inst1 = Class1()
|
|
199
|
+
inst2 = Class1()
|
|
200
|
+
|
|
201
|
+
msg_queue: list[tuple[object, Event1]] = []
|
|
202
|
+
|
|
203
|
+
def fn(publisher, event: Event1):
|
|
204
|
+
msg_queue.append((publisher, event))
|
|
205
|
+
|
|
206
|
+
inst1.event1.subscribe(fn)
|
|
207
|
+
inst2.event1.subscribe(fn)
|
|
208
|
+
|
|
209
|
+
inst1.event1.fire(Event1("msg",1))
|
|
210
|
+
inst2.event1.fire(Event1("msg",2))
|
|
211
|
+
|
|
212
|
+
assert len(msg_queue) == 2
|
|
213
|
+
assert msg_queue[0][0] is inst1
|
|
214
|
+
assert msg_queue[0][1].arg2 == 1
|
|
215
|
+
assert msg_queue[1][0] is inst2
|
|
216
|
+
assert msg_queue[1][1].arg2 == 2
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def test_instances_of_different_classes():
|
|
221
|
+
|
|
222
|
+
class Class1:
|
|
223
|
+
event1 = delegate(Channel[Event1])
|
|
224
|
+
|
|
225
|
+
class Class2:
|
|
226
|
+
event1 = delegate(Channel[Event1])
|
|
227
|
+
|
|
228
|
+
inst1 = Class1()
|
|
229
|
+
inst2 = Class2()
|
|
230
|
+
|
|
231
|
+
msg_queue: list[tuple[object, Event1]] = []
|
|
232
|
+
|
|
233
|
+
def fn(publisher, event: Event1):
|
|
234
|
+
msg_queue.append((publisher, event))
|
|
235
|
+
|
|
236
|
+
inst1.event1.subscribe(fn)
|
|
237
|
+
inst2.event1.subscribe(fn)
|
|
238
|
+
|
|
239
|
+
inst1.event1.fire(Event1("msg",1))
|
|
240
|
+
inst2.event1.fire(Event1("msg",2))
|
|
241
|
+
|
|
242
|
+
assert len(msg_queue) == 2
|
|
243
|
+
assert msg_queue[0][0] is inst1
|
|
244
|
+
assert msg_queue[0][1].arg2 == 1
|
|
245
|
+
assert msg_queue[1][0] is inst2
|
|
246
|
+
assert msg_queue[1][1].arg2 == 2
|
|
247
|
+
|
|
248
|
+
def test_readme_example1():
|
|
249
|
+
from delegate.pattern import delegate
|
|
250
|
+
from delegate.events import Channel, Event
|
|
251
|
+
|
|
252
|
+
class OnSomethingEvent(Event):
|
|
253
|
+
def __init__(self, what: str, why: str):
|
|
254
|
+
self.what = what
|
|
255
|
+
self.why = why
|
|
256
|
+
|
|
257
|
+
def __str__(self):
|
|
258
|
+
return f"{self.what} happened because of {self.why}"
|
|
259
|
+
|
|
260
|
+
class Class1:
|
|
261
|
+
on_something = delegate(Channel[OnSomethingEvent])
|
|
262
|
+
|
|
263
|
+
inst = Class1()
|
|
264
|
+
msg_queue: list[Event] = []
|
|
265
|
+
|
|
266
|
+
def fn(publisher, event: OnSomethingEvent):
|
|
267
|
+
msg_queue.append(event)
|
|
268
|
+
|
|
269
|
+
inst.on_something.subscribe(fn)
|
|
270
|
+
inst.on_something.fire(OnSomethingEvent("Something", "some other thing"))
|
|
271
|
+
|
|
272
|
+
event_msg = str(msg_queue[0]) # => "Something happened because of some other thing"
|
|
273
|
+
assert str(event_msg) == "Something happened because of some other thing"
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_readme_example2():
|
|
277
|
+
from delegate.events import Channel
|
|
278
|
+
|
|
279
|
+
channel = Channel[OnSomethingEvent]()
|
|
280
|
+
|
|
281
|
+
def handler(sender: object, event: OnSomethingEvent) -> None:
|
|
282
|
+
pass
|
|
283
|
+
|
|
284
|
+
channel.subscribe(handler)
|
|
285
|
+
assert True == channel.fire(OnSomethingEvent("Something", "some other thing"))
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def test_readme_example3():
|
|
289
|
+
from delegate.events import Channel
|
|
290
|
+
|
|
291
|
+
channel = Channel[OnSomethingEvent]()
|
|
292
|
+
|
|
293
|
+
msg_queue: list[Event] = []
|
|
294
|
+
|
|
295
|
+
def handler(sender: object, event: OnSomethingEvent) -> None:
|
|
296
|
+
msg_queue.append(event)
|
|
297
|
+
|
|
298
|
+
assert False == channel.fire(OnSomethingEvent("Something", "some other thing")) # -> False
|
|
299
|
+
assert False == channel.fire(OnSomethingEvent("Something", "some other thing"), ttl=10) # -> False - event stays in queue for 10 seconds though
|
|
300
|
+
|
|
301
|
+
channel.subscribe(handler)
|
|
302
|
+
|
|
303
|
+
assert True == channel.fire(OnSomethingEvent("Something", "some other thing")) # -> True
|
|
304
|
+
assert len(msg_queue) == 2
|