eventsourcing 9.3.5__tar.gz → 9.4.0a2__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.
Potentially problematic release.
This version of eventsourcing might be problematic. Click here for more details.
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/LICENSE +1 -1
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/PKG-INFO +9 -7
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/README.md +1 -1
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/application.py +26 -10
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/cipher.py +4 -2
- eventsourcing-9.4.0a2/eventsourcing/cryptography.py +96 -0
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/domain.py +29 -9
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/interface.py +23 -5
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/persistence.py +292 -71
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/popo.py +113 -32
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/postgres.py +265 -103
- eventsourcing-9.4.0a2/eventsourcing/projection.py +200 -0
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/sqlite.py +143 -36
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/system.py +64 -42
- eventsourcing-9.4.0a2/eventsourcing/tests/__init__.py +0 -0
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/tests/application.py +7 -12
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/tests/persistence.py +304 -75
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/utils.py +1 -1
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/pyproject.toml +32 -9
- eventsourcing-9.3.5/eventsourcing/__init__.py +0 -1
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/AUTHORS +0 -0
- {eventsourcing-9.3.5/eventsourcing/tests → eventsourcing-9.4.0a2/eventsourcing}/__init__.py +0 -0
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/compressor.py +0 -0
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/dispatch.py +0 -0
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/py.typed +0 -0
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/tests/domain.py +0 -0
- {eventsourcing-9.3.5 → eventsourcing-9.4.0a2}/eventsourcing/tests/postgres_utils.py +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: eventsourcing
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.4.0a2
|
|
4
4
|
Summary: Event sourcing in Python
|
|
5
5
|
Home-page: https://github.com/pyeventsourcing/eventsourcing
|
|
6
6
|
License: BSD 3-Clause
|
|
7
7
|
Keywords: event sourcing,event store,domain driven design,domain-driven design,ddd,cqrs,cqs
|
|
8
8
|
Author: John Bywater
|
|
9
9
|
Author-email: john.bywater@appropriatesoftware.net
|
|
10
|
-
Requires-Python: >=3.8
|
|
11
|
-
Classifier: Development Status ::
|
|
10
|
+
Requires-Python: >=3.8, !=2.7.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: Intended Audience :: Education
|
|
14
14
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -18,22 +18,24 @@ Classifier: Operating System :: OS Independent
|
|
|
18
18
|
Classifier: Programming Language :: Python
|
|
19
19
|
Classifier: Programming Language :: Python :: 3
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.8
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.10
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
24
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
25
24
|
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
26
26
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
27
|
Provides-Extra: crypto
|
|
28
|
+
Provides-Extra: cryptography
|
|
28
29
|
Provides-Extra: postgres
|
|
29
30
|
Requires-Dist: backports.zoneinfo ; python_version < "3.9"
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
31
|
+
Requires-Dist: cryptography (>=44.0,<44.1) ; extra == "cryptography"
|
|
32
|
+
Requires-Dist: psycopg[pool] (<=3.2.99999) ; extra == "postgres"
|
|
33
|
+
Requires-Dist: pycryptodome (>=3.22,<3.23) ; extra == "crypto"
|
|
32
34
|
Requires-Dist: typing_extensions
|
|
33
35
|
Project-URL: Repository, https://github.com/pyeventsourcing/eventsourcing
|
|
34
36
|
Description-Content-Type: text/markdown
|
|
35
37
|
|
|
36
|
-
[](https://github.com/pyeventsourcing/eventsourcing)
|
|
37
39
|
[](https://coveralls.io/github/pyeventsourcing/eventsourcing?branch=main)
|
|
38
40
|
[](https://eventsourcing.readthedocs.io/en/stable/)
|
|
39
41
|
[](https://pypi.org/project/eventsourcing/)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](https://github.com/pyeventsourcing/eventsourcing)
|
|
2
2
|
[](https://coveralls.io/github/pyeventsourcing/eventsourcing?branch=main)
|
|
3
3
|
[](https://eventsourcing.readthedocs.io/en/stable/)
|
|
4
4
|
[](https://pypi.org/project/eventsourcing/)
|
|
@@ -34,12 +34,11 @@ from eventsourcing.domain import (
|
|
|
34
34
|
DomainEventProtocol,
|
|
35
35
|
EventSourcingError,
|
|
36
36
|
MutableOrImmutableAggregate,
|
|
37
|
-
ProgrammingError,
|
|
38
37
|
Snapshot,
|
|
39
38
|
SnapshotProtocol,
|
|
40
39
|
TDomainEvent,
|
|
41
40
|
TMutableOrImmutableAggregate,
|
|
42
|
-
|
|
41
|
+
datetime_now_with_tzinfo,
|
|
43
42
|
)
|
|
44
43
|
from eventsourcing.persistence import (
|
|
45
44
|
ApplicationRecorder,
|
|
@@ -47,16 +46,18 @@ from eventsourcing.persistence import (
|
|
|
47
46
|
DecimalAsStr,
|
|
48
47
|
EventStore,
|
|
49
48
|
InfrastructureFactory,
|
|
49
|
+
JSONTranscoder,
|
|
50
50
|
Mapper,
|
|
51
51
|
Notification,
|
|
52
52
|
Recording,
|
|
53
53
|
Tracking,
|
|
54
|
+
TrackingRecorder,
|
|
54
55
|
Transcoder,
|
|
55
56
|
UUIDAsHex,
|
|
56
57
|
)
|
|
57
58
|
from eventsourcing.utils import Environment, EnvType, strtobool
|
|
58
59
|
|
|
59
|
-
if TYPE_CHECKING:
|
|
60
|
+
if TYPE_CHECKING:
|
|
60
61
|
from uuid import UUID
|
|
61
62
|
|
|
62
63
|
ProjectorFunction = Callable[
|
|
@@ -70,6 +71,10 @@ MutatorFunction = Callable[
|
|
|
70
71
|
]
|
|
71
72
|
|
|
72
73
|
|
|
74
|
+
class ProgrammingError(Exception):
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
|
|
73
78
|
def project_aggregate(
|
|
74
79
|
aggregate: TMutableOrImmutableAggregate | None,
|
|
75
80
|
domain_events: Iterable[DomainEventProtocol],
|
|
@@ -435,10 +440,12 @@ class NotificationLog(ABC):
|
|
|
435
440
|
@abstractmethod
|
|
436
441
|
def select(
|
|
437
442
|
self,
|
|
438
|
-
start: int,
|
|
443
|
+
start: int | None,
|
|
439
444
|
limit: int,
|
|
440
445
|
stop: int | None = None,
|
|
441
446
|
topics: Sequence[str] = (),
|
|
447
|
+
*,
|
|
448
|
+
inclusive_of_start: bool = True,
|
|
442
449
|
) -> List[Notification]:
|
|
443
450
|
"""
|
|
444
451
|
Returns a selection of
|
|
@@ -523,10 +530,12 @@ class LocalNotificationLog(NotificationLog):
|
|
|
523
530
|
|
|
524
531
|
def select(
|
|
525
532
|
self,
|
|
526
|
-
start: int,
|
|
533
|
+
start: int | None,
|
|
527
534
|
limit: int,
|
|
528
535
|
stop: int | None = None,
|
|
529
536
|
topics: Sequence[str] = (),
|
|
537
|
+
*,
|
|
538
|
+
inclusive_of_start: bool = True,
|
|
530
539
|
) -> List[Notification]:
|
|
531
540
|
"""
|
|
532
541
|
Returns a selection of
|
|
@@ -539,7 +548,11 @@ class LocalNotificationLog(NotificationLog):
|
|
|
539
548
|
)
|
|
540
549
|
raise ValueError(msg)
|
|
541
550
|
return self.recorder.select_notifications(
|
|
542
|
-
start=start,
|
|
551
|
+
start=start,
|
|
552
|
+
limit=limit,
|
|
553
|
+
stop=stop,
|
|
554
|
+
topics=topics,
|
|
555
|
+
inclusive_of_start=inclusive_of_start,
|
|
543
556
|
)
|
|
544
557
|
|
|
545
558
|
@staticmethod
|
|
@@ -685,7 +698,9 @@ class Application:
|
|
|
685
698
|
_env.update(env)
|
|
686
699
|
return Environment(name, _env)
|
|
687
700
|
|
|
688
|
-
def construct_factory(
|
|
701
|
+
def construct_factory(
|
|
702
|
+
self, env: Environment
|
|
703
|
+
) -> InfrastructureFactory[TrackingRecorder]:
|
|
689
704
|
"""
|
|
690
705
|
Constructs an :class:`~eventsourcing.persistence.InfrastructureFactory`
|
|
691
706
|
for use by the application.
|
|
@@ -705,10 +720,11 @@ class Application:
|
|
|
705
720
|
for use by the application.
|
|
706
721
|
"""
|
|
707
722
|
transcoder = self.factory.transcoder()
|
|
708
|
-
|
|
723
|
+
if isinstance(transcoder, JSONTranscoder):
|
|
724
|
+
self.register_transcodings(transcoder)
|
|
709
725
|
return transcoder
|
|
710
726
|
|
|
711
|
-
def register_transcodings(self, transcoder:
|
|
727
|
+
def register_transcodings(self, transcoder: JSONTranscoder) -> None:
|
|
712
728
|
"""
|
|
713
729
|
Registers :class:`~eventsourcing.persistence.Transcoding`
|
|
714
730
|
objects on given :class:`~eventsourcing.persistence.JSONTranscoder`.
|
|
@@ -967,7 +983,7 @@ class EventSourcedLog(Generic[TDomainEvent]):
|
|
|
967
983
|
return logged_cls( # type: ignore
|
|
968
984
|
originator_id=self.originator_id,
|
|
969
985
|
originator_version=next_originator_version,
|
|
970
|
-
timestamp=
|
|
986
|
+
timestamp=datetime_now_with_tzinfo(),
|
|
971
987
|
**kwargs,
|
|
972
988
|
)
|
|
973
989
|
|
|
@@ -10,13 +10,14 @@ from Crypto.Cipher.AES import key_size
|
|
|
10
10
|
|
|
11
11
|
from eventsourcing.persistence import Cipher
|
|
12
12
|
|
|
13
|
-
if TYPE_CHECKING:
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
14
|
from eventsourcing.utils import Environment
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class AESCipher(Cipher):
|
|
18
18
|
"""
|
|
19
|
-
Cipher strategy that uses AES cipher in GCM mode
|
|
19
|
+
Cipher strategy that uses AES cipher (in GCM mode)
|
|
20
|
+
from the Python pycryptodome package.
|
|
20
21
|
"""
|
|
21
22
|
|
|
22
23
|
CIPHER_KEY = "CIPHER_KEY"
|
|
@@ -71,6 +72,7 @@ class AESCipher(Cipher):
|
|
|
71
72
|
|
|
72
73
|
# Return ciphertext.
|
|
73
74
|
return nonce + tag + encrypted
|
|
75
|
+
# return nonce + tag + encrypted
|
|
74
76
|
|
|
75
77
|
def construct_cipher(self, nonce: bytes) -> GcmMode:
|
|
76
78
|
cipher = AES.new(
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from base64 import b64decode, b64encode
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from cryptography.exceptions import InvalidTag
|
|
8
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
9
|
+
|
|
10
|
+
from eventsourcing.persistence import Cipher
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from eventsourcing.utils import Environment
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AESCipher(Cipher):
|
|
17
|
+
"""
|
|
18
|
+
Cipher strategy that uses AES cipher (in GCM mode)
|
|
19
|
+
from the Python cryptography package.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
CIPHER_KEY = "CIPHER_KEY"
|
|
23
|
+
KEY_SIZES = (16, 24, 32)
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def create_key(num_bytes: int) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Creates AES cipher key, with length num_bytes.
|
|
29
|
+
|
|
30
|
+
:param num_bytes: An int value, either 16, 24, or 32.
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
AESCipher.check_key_size(num_bytes)
|
|
34
|
+
key = AESGCM.generate_key(num_bytes * 8)
|
|
35
|
+
return b64encode(key).decode("utf8")
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def check_key_size(num_bytes: int) -> None:
|
|
39
|
+
if num_bytes not in AESCipher.KEY_SIZES:
|
|
40
|
+
msg = f"Invalid key size: {num_bytes} not in {AESCipher.KEY_SIZES}"
|
|
41
|
+
raise ValueError(msg)
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def random_bytes(num_bytes: int) -> bytes:
|
|
45
|
+
return os.urandom(num_bytes)
|
|
46
|
+
|
|
47
|
+
def __init__(self, environment: Environment):
|
|
48
|
+
"""
|
|
49
|
+
Initialises AES cipher with ``cipher_key``.
|
|
50
|
+
|
|
51
|
+
:param str cipher_key: 16, 24, or 32 bytes encoded as base64
|
|
52
|
+
"""
|
|
53
|
+
cipher_key = environment.get(self.CIPHER_KEY)
|
|
54
|
+
if not cipher_key:
|
|
55
|
+
msg = f"'{self.CIPHER_KEY}' not in env"
|
|
56
|
+
raise OSError(msg)
|
|
57
|
+
key = b64decode(cipher_key.encode("utf8"))
|
|
58
|
+
AESCipher.check_key_size(len(key))
|
|
59
|
+
self.key = key
|
|
60
|
+
|
|
61
|
+
def encrypt(self, plaintext: bytes) -> bytes:
|
|
62
|
+
"""Return ciphertext for given plaintext."""
|
|
63
|
+
|
|
64
|
+
# Construct AES-GCM cipher, with 96-bit nonce.
|
|
65
|
+
aesgcm = AESGCM(self.key)
|
|
66
|
+
nonce = AESCipher.random_bytes(12)
|
|
67
|
+
res = aesgcm.encrypt(nonce, plaintext, None)
|
|
68
|
+
# Put tag at the front for compatibility with eventsourcing.crypto.AESCipher.
|
|
69
|
+
tag = res[-16:]
|
|
70
|
+
encrypted = res[:-16]
|
|
71
|
+
return nonce + tag + encrypted
|
|
72
|
+
|
|
73
|
+
def decrypt(self, ciphertext: bytes) -> bytes:
|
|
74
|
+
"""Return plaintext for given ciphertext."""
|
|
75
|
+
|
|
76
|
+
# Split out the nonce, tag, and encrypted data.
|
|
77
|
+
nonce = ciphertext[:12]
|
|
78
|
+
if len(nonce) != 12:
|
|
79
|
+
msg = "Damaged cipher text: invalid nonce length"
|
|
80
|
+
raise ValueError(msg)
|
|
81
|
+
|
|
82
|
+
# Expect tag at the front.
|
|
83
|
+
tag = ciphertext[12:28]
|
|
84
|
+
if len(tag) != 16:
|
|
85
|
+
msg = "Damaged cipher text: invalid tag length"
|
|
86
|
+
raise ValueError(msg)
|
|
87
|
+
encrypted = ciphertext[28:]
|
|
88
|
+
|
|
89
|
+
aesgcm = AESGCM(self.key)
|
|
90
|
+
try:
|
|
91
|
+
plaintext = aesgcm.decrypt(nonce, encrypted + tag, None)
|
|
92
|
+
except InvalidTag as e:
|
|
93
|
+
msg = "Invalid cipher tag"
|
|
94
|
+
raise ValueError(msg) from e
|
|
95
|
+
# Decrypt and verify.
|
|
96
|
+
return plaintext
|
|
@@ -25,10 +25,20 @@ from typing import (
|
|
|
25
25
|
runtime_checkable,
|
|
26
26
|
)
|
|
27
27
|
from uuid import UUID, uuid4
|
|
28
|
+
from warnings import warn
|
|
28
29
|
|
|
29
30
|
from eventsourcing.utils import get_method_name, get_topic, resolve_topic
|
|
30
31
|
|
|
31
32
|
TZINFO: tzinfo = resolve_topic(os.getenv("TZINFO_TOPIC", "datetime:timezone.utc"))
|
|
33
|
+
"""
|
|
34
|
+
A Python :py:obj:`tzinfo` object that defaults to UTC (:py:obj:`timezone.utc`). Used
|
|
35
|
+
as the timezone argument in :func:`~eventsourcing.domain.datetime_now_with_tzinfo`.
|
|
36
|
+
|
|
37
|
+
Set environment variable ``TZINFO_TOPIC`` to the topic of a different :py:obj:`tzinfo`
|
|
38
|
+
object so that all your domain model event timestamps are located in that timezone
|
|
39
|
+
(not recommended). It is generally recommended to locate all timestamps in the UTC
|
|
40
|
+
domain and convert to local timezones when presenting values in user interfaces.
|
|
41
|
+
"""
|
|
32
42
|
|
|
33
43
|
|
|
34
44
|
@runtime_checkable
|
|
@@ -153,13 +163,27 @@ class CanMutateProtocol(DomainEventProtocol, Protocol[TMutableOrImmutableAggrega
|
|
|
153
163
|
"""
|
|
154
164
|
|
|
155
165
|
|
|
156
|
-
def
|
|
166
|
+
def datetime_now_with_tzinfo() -> datetime:
|
|
157
167
|
"""
|
|
158
168
|
Constructs a timezone-aware :class:`datetime` object for the current date and time.
|
|
169
|
+
|
|
170
|
+
Uses :py:obj:`TZINFO` as the timezone.
|
|
159
171
|
"""
|
|
160
172
|
return datetime.now(tz=TZINFO)
|
|
161
173
|
|
|
162
174
|
|
|
175
|
+
def create_utc_datetime_now() -> datetime:
|
|
176
|
+
"""
|
|
177
|
+
Deprected in favour of :func:`~eventsourcing.domain.datetime_now_with_tzinfo`.
|
|
178
|
+
"""
|
|
179
|
+
msg = (
|
|
180
|
+
"'create_utc_datetime_now()' is deprecated, "
|
|
181
|
+
"use 'datetime_now_with_tzinfo()' instead"
|
|
182
|
+
)
|
|
183
|
+
warn(msg, DeprecationWarning, stacklevel=2)
|
|
184
|
+
return datetime_now_with_tzinfo()
|
|
185
|
+
|
|
186
|
+
|
|
163
187
|
class CanCreateTimestamp:
|
|
164
188
|
"""
|
|
165
189
|
Provides a create_timestamp() method to subclasses.
|
|
@@ -171,7 +195,7 @@ class CanCreateTimestamp:
|
|
|
171
195
|
Constructs a timezone-aware :class:`datetime` object
|
|
172
196
|
representing when an event occurred.
|
|
173
197
|
"""
|
|
174
|
-
return
|
|
198
|
+
return datetime_now_with_tzinfo()
|
|
175
199
|
|
|
176
200
|
|
|
177
201
|
TAggregate = TypeVar("TAggregate", bound="Aggregate")
|
|
@@ -386,7 +410,7 @@ def _spec_filter_kwargs_for_method_params(method: Callable[..., Any]) -> set[str
|
|
|
386
410
|
return set(method_signature.parameters)
|
|
387
411
|
|
|
388
412
|
|
|
389
|
-
if TYPE_CHECKING:
|
|
413
|
+
if TYPE_CHECKING:
|
|
390
414
|
EventSpecType = Union[str, Type[CanMutateAggregate]]
|
|
391
415
|
|
|
392
416
|
CommandMethod = Callable[..., None]
|
|
@@ -668,6 +692,8 @@ class UnboundCommandMethodDecorator:
|
|
|
668
692
|
self.__qualname__ = event_decorator.decorated_method.__qualname__
|
|
669
693
|
self.__annotations__ = event_decorator.decorated_method.__annotations__
|
|
670
694
|
self.__doc__ = event_decorator.decorated_method.__doc__
|
|
695
|
+
# self.__wrapped__ = event_decorator.decorated_method
|
|
696
|
+
# functools.update_wrapper(self, event_decorator.decorated_method)
|
|
671
697
|
|
|
672
698
|
def __call__(self, *args: Any, **kwargs: Any) -> None:
|
|
673
699
|
# Expect first argument is an aggregate instance.
|
|
@@ -1526,12 +1552,6 @@ class VersionError(OriginatorVersionError):
|
|
|
1526
1552
|
|
|
1527
1553
|
|
|
1528
1554
|
class SnapshotProtocol(DomainEventProtocol, Protocol):
|
|
1529
|
-
@property
|
|
1530
|
-
def topic(self) -> str:
|
|
1531
|
-
"""
|
|
1532
|
-
Snapshots have a read-only 'topic'.
|
|
1533
|
-
"""
|
|
1534
|
-
|
|
1535
1555
|
@property
|
|
1536
1556
|
def state(self) -> Dict[str, Any]:
|
|
1537
1557
|
"""
|
|
@@ -25,7 +25,12 @@ class NotificationLogInterface(ABC):
|
|
|
25
25
|
|
|
26
26
|
@abstractmethod
|
|
27
27
|
def get_notifications(
|
|
28
|
-
self,
|
|
28
|
+
self,
|
|
29
|
+
start: int | None,
|
|
30
|
+
limit: int,
|
|
31
|
+
topics: Sequence[str] = (),
|
|
32
|
+
*,
|
|
33
|
+
inclusive_of_start: bool = True,
|
|
29
34
|
) -> str:
|
|
30
35
|
"""
|
|
31
36
|
Returns a serialised list of :class:`~eventsourcing.persistence.Notification`
|
|
@@ -68,10 +73,18 @@ class NotificationLogJSONService(NotificationLogInterface, Generic[TApplication]
|
|
|
68
73
|
)
|
|
69
74
|
|
|
70
75
|
def get_notifications(
|
|
71
|
-
self,
|
|
76
|
+
self,
|
|
77
|
+
start: int | None,
|
|
78
|
+
limit: int,
|
|
79
|
+
topics: Sequence[str] = (),
|
|
80
|
+
*,
|
|
81
|
+
inclusive_of_start: bool = True,
|
|
72
82
|
) -> str:
|
|
73
83
|
notifications = self.app.notification_log.select(
|
|
74
|
-
start=start,
|
|
84
|
+
start=start,
|
|
85
|
+
limit=limit,
|
|
86
|
+
topics=topics,
|
|
87
|
+
inclusive_of_start=inclusive_of_start,
|
|
75
88
|
)
|
|
76
89
|
return json.dumps(
|
|
77
90
|
[
|
|
@@ -123,10 +136,12 @@ class NotificationLogJSONClient(NotificationLog):
|
|
|
123
136
|
|
|
124
137
|
def select(
|
|
125
138
|
self,
|
|
126
|
-
start: int,
|
|
139
|
+
start: int | None,
|
|
127
140
|
limit: int,
|
|
128
141
|
_: int | None = None,
|
|
129
142
|
topics: Sequence[str] = (),
|
|
143
|
+
*,
|
|
144
|
+
inclusive_of_start: bool = True,
|
|
130
145
|
) -> List[Notification]:
|
|
131
146
|
"""
|
|
132
147
|
Returns a selection of
|
|
@@ -143,7 +158,10 @@ class NotificationLogJSONClient(NotificationLog):
|
|
|
143
158
|
)
|
|
144
159
|
for item in json.loads(
|
|
145
160
|
self.interface.get_notifications(
|
|
146
|
-
start=start,
|
|
161
|
+
start=start,
|
|
162
|
+
limit=limit,
|
|
163
|
+
topics=topics,
|
|
164
|
+
inclusive_of_start=inclusive_of_start,
|
|
147
165
|
)
|
|
148
166
|
)
|
|
149
167
|
]
|