delegate-events 0.0.0__py3-none-any.whl

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.
@@ -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,5 @@
1
+ class ChannelClosedError(Exception):
2
+ """
3
+ The ChannelClosedError exception is raised when accessing functions etc. on a closed channel.
4
+ """
5
+ pass
@@ -0,0 +1,7 @@
1
+
2
+ class Event:
3
+ __slots__ = [ ]
4
+
5
+
6
+
7
+
@@ -0,0 +1,6 @@
1
+
2
+ class HandlerAlreadySubscribedError(Exception):
3
+ """
4
+ The HandlerAlreadySubscribedError exception is raised when an event handler is already subscribed to the channel.
5
+ """
6
+ pass
@@ -0,0 +1,6 @@
1
+
2
+ class HandlerNotSubscribedError(Exception):
3
+ """
4
+ The HandlerNotSubscribedError exception is raised when an event handler is not subscribed to the channel.
5
+ """
6
+ pass
@@ -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
+ [![Test](https://github.com/apmadsen/delegate-events/actions/workflows/python-test.yml/badge.svg)](https://github.com/apmadsen/delegate-events/actions/workflows/python-test.yml)
35
+ [![Coverage](https://github.com/apmadsen/delegate-events/actions/workflows/python-test-coverage.yml/badge.svg)](https://github.com/apmadsen/delegate-events/actions/workflows/python-test-coverage.yml)
36
+ [![Stable Version](https://img.shields.io/pypi/v/delegate-events?label=stable&sort=semver&color=blue)](https://github.com/apmadsen/delegate-events/releases)
37
+ ![Pre-release Version](https://img.shields.io/github/v/release/apmadsen/delegate-events?label=pre-release&include_prereleases&sort=semver&color=blue)
38
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/delegate-events)
39
+ [![PyPI Downloads](https://static.pepy.tech/badge/delegate-events/week)](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,12 @@
1
+ delegate/events/__init__.py,sha256=p9T6xaF__aQ5iZEd-E7AGqRjd9mb-JRlNxSFk48EnY4,489
2
+ delegate/events/core/channel.py,sha256=zXfs-dw8U91TQUeyimutRj5Jnb21WUEXVkZw--yudsE,3886
3
+ delegate/events/core/channel_closed_error.py,sha256=wKs0nZvCY6iKba93S4cEEjAPggmmA6HQk6LhlbEVvlM,159
4
+ delegate/events/core/event.py,sha256=5v53F15GvHN7bjz0dFoYS8s6gcO2FQJ7cylpA-pNilc,38
5
+ delegate/events/core/handler_already_subscribed_error.py,sha256=M-abPb0GfOOdRdN2Wfrfn8dim3JZp-oaEeeUfTH4Uqc,191
6
+ delegate/events/core/handler_not_subscribed_error.py,sha256=_qTOxqklHYnVpjQ1txw2n4hvef2_6i7h1O6E5z7mEj4,179
7
+ delegate/events/core/queued_event.py,sha256=sA41t0ODtLu8Sdn0Ty_dCi72luOcqPo6jydB4O_gH64,432
8
+ delegate_events-0.0.0.dist-info/licenses/LICENSE,sha256=arWCq6_BrCqO8UFdTrMBI356o9Qyqsp_RPvpthW5Yfk,1066
9
+ delegate_events-0.0.0.dist-info/METADATA,sha256=V77cfjTaBClQ-HIDZTdRUv9qKRyY969N9Hsz3Fir_kU,5600
10
+ delegate_events-0.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ delegate_events-0.0.0.dist-info/top_level.txt,sha256=l_igR_YcbGXNRiMQ2yVnuPO7HyGXHvVWrifyAAg3uX8,9
12
+ delegate_events-0.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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 @@
1
+ delegate