eventsourcing 9.3.0__py3-none-any.whl → 9.3.0b1__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.
Potentially problematic release.
This version of eventsourcing might be problematic. Click here for more details.
- eventsourcing/application.py +0 -10
- eventsourcing/examples/aggregate4/domainmodel.py +28 -14
- eventsourcing/examples/contentmanagementsystem/test_system.py +113 -119
- eventsourcing/examples/searchablecontent/test_application.py +5 -4
- eventsourcing/examples/searchablecontent/test_recorder.py +5 -4
- eventsourcing/examples/searchabletimestamps/test_searchabletimestamps.py +5 -8
- eventsourcing/postgres.py +22 -28
- eventsourcing/system.py +0 -10
- eventsourcing/tests/docs_tests/test_docs.py +10 -10
- eventsourcing/tests/domain_tests/test_aggregate.py +0 -7
- eventsourcing/tests/persistence.py +0 -3
- eventsourcing/tests/persistence_tests/test_postgres.py +106 -104
- eventsourcing/tests/system_tests/test_runner.py +17 -17
- eventsourcing/tests/system_tests/test_system.py +4 -1
- {eventsourcing-9.3.0.dist-info → eventsourcing-9.3.0b1.dist-info}/METADATA +4 -6
- {eventsourcing-9.3.0.dist-info → eventsourcing-9.3.0b1.dist-info}/RECORD +18 -19
- {eventsourcing-9.3.0.dist-info → eventsourcing-9.3.0b1.dist-info}/WHEEL +1 -1
- eventsourcing-9.3.0.dist-info/AUTHORS +0 -10
- {eventsourcing-9.3.0.dist-info → eventsourcing-9.3.0b1.dist-info}/LICENSE +0 -0
eventsourcing/application.py
CHANGED
|
@@ -915,16 +915,6 @@ class AggregateNotFoundError(EventSourcingError):
|
|
|
915
915
|
"""
|
|
916
916
|
|
|
917
917
|
|
|
918
|
-
class AggregateNotFound(AggregateNotFoundError): # noqa: N818
|
|
919
|
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
920
|
-
warn(
|
|
921
|
-
"AggregateNotFound is deprecated, use AggregateNotFoundError instead",
|
|
922
|
-
DeprecationWarning,
|
|
923
|
-
stacklevel=2,
|
|
924
|
-
)
|
|
925
|
-
super().__init__(*args, **kwargs)
|
|
926
|
-
|
|
927
|
-
|
|
928
918
|
class EventSourcedLog(Generic[TDomainEvent]):
|
|
929
919
|
"""
|
|
930
920
|
Constructs a sequence of domain events, like an aggregate.
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
4
|
+
from collections import defaultdict
|
|
3
5
|
from dataclasses import dataclass
|
|
4
6
|
from datetime import datetime, timezone
|
|
5
|
-
from typing import Any, Iterable, List, Type, TypeVar, cast
|
|
7
|
+
from typing import Any, ClassVar, Dict, Iterable, List, Type, TypeVar, cast
|
|
6
8
|
from uuid import UUID, uuid4
|
|
7
9
|
|
|
8
10
|
from eventsourcing.dispatch import singledispatchmethod
|
|
@@ -27,7 +29,6 @@ class Aggregate:
|
|
|
27
29
|
id: UUID
|
|
28
30
|
version: int
|
|
29
31
|
created_on: datetime
|
|
30
|
-
_pending_events: List[DomainEvent]
|
|
31
32
|
|
|
32
33
|
def __init__(self, event: DomainEvent):
|
|
33
34
|
self.id = event.originator_id
|
|
@@ -46,15 +47,15 @@ class Aggregate:
|
|
|
46
47
|
timestamp=event_class.create_timestamp(),
|
|
47
48
|
)
|
|
48
49
|
new_event = event_class(**kwargs)
|
|
49
|
-
self.
|
|
50
|
-
self.
|
|
50
|
+
self.apply(new_event)
|
|
51
|
+
self.pending_events.append(new_event)
|
|
51
52
|
|
|
52
53
|
@singledispatchmethod
|
|
53
|
-
def
|
|
54
|
+
def apply(self, event: DomainEvent) -> None:
|
|
54
55
|
"""Applies event to aggregate."""
|
|
55
56
|
|
|
56
57
|
def collect_events(self) -> List[DomainEvent]:
|
|
57
|
-
events, self.
|
|
58
|
+
events, self.pending_events = self.pending_events, []
|
|
58
59
|
return events
|
|
59
60
|
|
|
60
61
|
@classmethod
|
|
@@ -63,12 +64,25 @@ class Aggregate:
|
|
|
63
64
|
_: TAggregate | None,
|
|
64
65
|
events: Iterable[DomainEvent],
|
|
65
66
|
) -> TAggregate | None:
|
|
66
|
-
aggregate
|
|
67
|
-
aggregate._pending_events = []
|
|
67
|
+
aggregate = object.__new__(cls)
|
|
68
68
|
for event in events:
|
|
69
|
-
aggregate.
|
|
69
|
+
aggregate.apply(event)
|
|
70
70
|
return aggregate
|
|
71
71
|
|
|
72
|
+
@property
|
|
73
|
+
def pending_events(self) -> List[DomainEvent]:
|
|
74
|
+
return type(self).__pending_events[id(self)]
|
|
75
|
+
|
|
76
|
+
@pending_events.setter
|
|
77
|
+
def pending_events(self, pending_events: List[DomainEvent]) -> None:
|
|
78
|
+
type(self).__pending_events[id(self)] = pending_events
|
|
79
|
+
|
|
80
|
+
__pending_events: ClassVar[Dict[int, List[DomainEvent]]] = defaultdict(list)
|
|
81
|
+
|
|
82
|
+
def __del__(self) -> None:
|
|
83
|
+
with contextlib.suppress(KeyError):
|
|
84
|
+
type(self).__pending_events.pop(id(self))
|
|
85
|
+
|
|
72
86
|
|
|
73
87
|
class Dog(Aggregate):
|
|
74
88
|
@dataclass(frozen=True)
|
|
@@ -88,27 +102,27 @@ class Dog(Aggregate):
|
|
|
88
102
|
name=name,
|
|
89
103
|
)
|
|
90
104
|
dog = cast(Dog, cls.projector(None, [event]))
|
|
91
|
-
dog.
|
|
105
|
+
dog.pending_events.append(event)
|
|
92
106
|
return dog
|
|
93
107
|
|
|
94
108
|
def add_trick(self, trick: str) -> None:
|
|
95
109
|
self.trigger_event(self.TrickAdded, trick=trick)
|
|
96
110
|
|
|
97
111
|
@singledispatchmethod
|
|
98
|
-
def
|
|
112
|
+
def apply(self, event: DomainEvent) -> None:
|
|
99
113
|
"""Applies event to aggregate."""
|
|
100
114
|
|
|
101
|
-
@
|
|
115
|
+
@apply.register(Registered)
|
|
102
116
|
def _(self, event: Registered) -> None:
|
|
103
117
|
super().__init__(event)
|
|
104
118
|
self.name = event.name
|
|
105
119
|
self.tricks: List[str] = []
|
|
106
120
|
|
|
107
|
-
@
|
|
121
|
+
@apply.register(TrickAdded)
|
|
108
122
|
def _(self, event: TrickAdded) -> None:
|
|
109
123
|
self.tricks.append(event.trick)
|
|
110
124
|
self.version = event.originator_version
|
|
111
125
|
|
|
112
|
-
@
|
|
126
|
+
@apply.register(Snapshot)
|
|
113
127
|
def _(self, event: Snapshot) -> None:
|
|
114
128
|
self.__dict__.update(event.state)
|
|
@@ -23,119 +23,112 @@ class ContentManagementSystemTestCase(TestCase):
|
|
|
23
23
|
env: ClassVar[Dict[str, str]] = {}
|
|
24
24
|
|
|
25
25
|
def test_system(self) -> None:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
# Search for alternative words, expect two results.
|
|
133
|
-
page_ids = search_index_app.search("rose OR zebra")
|
|
134
|
-
pages = [
|
|
135
|
-
content_management_app.get_page_by_id(page_id) for page_id in page_ids
|
|
136
|
-
]
|
|
137
|
-
self.assertEqual(2, len(pages))
|
|
138
|
-
self.assertEqual(["animals", "plants"], sorted(p["slug"] for p in pages))
|
|
26
|
+
runner = SingleThreadedRunner(system=ContentManagementSystem(), env=self.env)
|
|
27
|
+
runner.start()
|
|
28
|
+
|
|
29
|
+
content_management_app = runner.get(ContentManagementApplication)
|
|
30
|
+
search_index_app = runner.get(SearchIndexApplication)
|
|
31
|
+
|
|
32
|
+
# Set user_id context variable.
|
|
33
|
+
user_id = uuid4()
|
|
34
|
+
user_id_cvar.set(user_id)
|
|
35
|
+
|
|
36
|
+
# Create empty pages.
|
|
37
|
+
content_management_app.create_page(title="Animals", slug="animals")
|
|
38
|
+
content_management_app.create_page(title="Plants", slug="plants")
|
|
39
|
+
content_management_app.create_page(title="Minerals", slug="minerals")
|
|
40
|
+
|
|
41
|
+
# Search, expect no results.
|
|
42
|
+
self.assertEqual(0, len(search_index_app.search("cat")))
|
|
43
|
+
self.assertEqual(0, len(search_index_app.search("rose")))
|
|
44
|
+
self.assertEqual(0, len(search_index_app.search("calcium")))
|
|
45
|
+
|
|
46
|
+
# Update the pages.
|
|
47
|
+
content_management_app.update_body(slug="animals", body="cat")
|
|
48
|
+
content_management_app.update_body(slug="plants", body="rose")
|
|
49
|
+
content_management_app.update_body(slug="minerals", body="calcium")
|
|
50
|
+
|
|
51
|
+
# Search for single words.
|
|
52
|
+
page_ids = search_index_app.search("cat")
|
|
53
|
+
self.assertEqual(1, len(page_ids))
|
|
54
|
+
page = content_management_app.get_page_by_id(page_ids[0])
|
|
55
|
+
self.assertEqual(page["slug"], "animals")
|
|
56
|
+
self.assertEqual(page["body"], "cat")
|
|
57
|
+
|
|
58
|
+
page_ids = search_index_app.search("rose")
|
|
59
|
+
self.assertEqual(1, len(page_ids))
|
|
60
|
+
page = content_management_app.get_page_by_id(page_ids[0])
|
|
61
|
+
self.assertEqual(page["slug"], "plants")
|
|
62
|
+
self.assertEqual(page["body"], "rose")
|
|
63
|
+
|
|
64
|
+
page_ids = search_index_app.search("calcium")
|
|
65
|
+
self.assertEqual(1, len(page_ids))
|
|
66
|
+
page = content_management_app.get_page_by_id(page_ids[0])
|
|
67
|
+
self.assertEqual(page["slug"], "minerals")
|
|
68
|
+
self.assertEqual(page["body"], "calcium")
|
|
69
|
+
|
|
70
|
+
self.assertEqual(len(search_index_app.search("dog")), 0)
|
|
71
|
+
self.assertEqual(len(search_index_app.search("bluebell")), 0)
|
|
72
|
+
self.assertEqual(len(search_index_app.search("zinc")), 0)
|
|
73
|
+
|
|
74
|
+
# Update the pages again.
|
|
75
|
+
content_management_app.update_body(slug="animals", body="cat dog zebra")
|
|
76
|
+
content_management_app.update_body(slug="plants", body="bluebell rose jasmine")
|
|
77
|
+
content_management_app.update_body(slug="minerals", body="iron zinc calcium")
|
|
78
|
+
|
|
79
|
+
# Search for single words.
|
|
80
|
+
page_ids = search_index_app.search("cat")
|
|
81
|
+
self.assertEqual(1, len(page_ids))
|
|
82
|
+
page = content_management_app.get_page_by_id(page_ids[0])
|
|
83
|
+
self.assertEqual(page["slug"], "animals")
|
|
84
|
+
self.assertEqual(page["body"], "cat dog zebra")
|
|
85
|
+
|
|
86
|
+
page_ids = search_index_app.search("rose")
|
|
87
|
+
self.assertEqual(1, len(page_ids))
|
|
88
|
+
page = content_management_app.get_page_by_id(page_ids[0])
|
|
89
|
+
self.assertEqual(page["slug"], "plants")
|
|
90
|
+
self.assertEqual(page["body"], "bluebell rose jasmine")
|
|
91
|
+
|
|
92
|
+
page_ids = search_index_app.search("calcium")
|
|
93
|
+
self.assertEqual(1, len(page_ids))
|
|
94
|
+
page = content_management_app.get_page_by_id(page_ids[0])
|
|
95
|
+
self.assertEqual(page["slug"], "minerals")
|
|
96
|
+
self.assertEqual(page["body"], "iron zinc calcium")
|
|
97
|
+
|
|
98
|
+
page_ids = search_index_app.search("dog")
|
|
99
|
+
self.assertEqual(1, len(page_ids))
|
|
100
|
+
page = content_management_app.get_page_by_id(page_ids[0])
|
|
101
|
+
self.assertEqual(page["slug"], "animals")
|
|
102
|
+
self.assertEqual(page["body"], "cat dog zebra")
|
|
103
|
+
|
|
104
|
+
page_ids = search_index_app.search("bluebell")
|
|
105
|
+
self.assertEqual(1, len(page_ids))
|
|
106
|
+
page = content_management_app.get_page_by_id(page_ids[0])
|
|
107
|
+
self.assertEqual(page["slug"], "plants")
|
|
108
|
+
self.assertEqual(page["body"], "bluebell rose jasmine")
|
|
109
|
+
|
|
110
|
+
page_ids = search_index_app.search("zinc")
|
|
111
|
+
self.assertEqual(1, len(page_ids))
|
|
112
|
+
page = content_management_app.get_page_by_id(page_ids[0])
|
|
113
|
+
self.assertEqual(page["slug"], "minerals")
|
|
114
|
+
self.assertEqual(page["body"], "iron zinc calcium")
|
|
115
|
+
|
|
116
|
+
# Search for multiple words in same page.
|
|
117
|
+
page_ids = search_index_app.search("dog cat")
|
|
118
|
+
self.assertEqual(1, len(page_ids))
|
|
119
|
+
page = content_management_app.get_page_by_id(page_ids[0])
|
|
120
|
+
self.assertEqual(page["slug"], "animals")
|
|
121
|
+
self.assertEqual(page["body"], "cat dog zebra")
|
|
122
|
+
|
|
123
|
+
# Search for multiple words in same page, expect no results.
|
|
124
|
+
page_ids = search_index_app.search("rose zebra")
|
|
125
|
+
self.assertEqual(0, len(page_ids))
|
|
126
|
+
|
|
127
|
+
# Search for alternative words, expect two results.
|
|
128
|
+
page_ids = search_index_app.search("rose OR zebra")
|
|
129
|
+
pages = [content_management_app.get_page_by_id(page_id) for page_id in page_ids]
|
|
130
|
+
self.assertEqual(2, len(pages))
|
|
131
|
+
self.assertEqual(["animals", "plants"], sorted(p["slug"] for p in pages))
|
|
139
132
|
|
|
140
133
|
|
|
141
134
|
class TestWithSQLite(ContentManagementSystemTestCase):
|
|
@@ -164,17 +157,18 @@ class TestWithPostgres(ContentManagementSystemTestCase):
|
|
|
164
157
|
super().tearDown()
|
|
165
158
|
|
|
166
159
|
def drop_tables(self) -> None:
|
|
167
|
-
|
|
160
|
+
db = PostgresDatastore(
|
|
168
161
|
self.env["POSTGRES_DBNAME"],
|
|
169
162
|
self.env["POSTGRES_HOST"],
|
|
170
163
|
self.env["POSTGRES_PORT"],
|
|
171
164
|
self.env["POSTGRES_USER"],
|
|
172
165
|
self.env["POSTGRES_PASSWORD"],
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
166
|
+
)
|
|
167
|
+
drop_postgres_table(db, "public.contentmanagementapplication_events")
|
|
168
|
+
drop_postgres_table(db, "public.pages_projection_example")
|
|
169
|
+
drop_postgres_table(db, "public.searchindexapplication_events")
|
|
170
|
+
drop_postgres_table(db, "public.searchindexapplication_tracking")
|
|
171
|
+
db.close()
|
|
178
172
|
|
|
179
173
|
|
|
180
174
|
del ContentManagementSystemTestCase
|
|
@@ -96,15 +96,16 @@ class TestWithPostgres(SearchableContentApplicationTestCase):
|
|
|
96
96
|
super().tearDown()
|
|
97
97
|
|
|
98
98
|
def drop_tables(self) -> None:
|
|
99
|
-
|
|
99
|
+
db = PostgresDatastore(
|
|
100
100
|
os.environ["POSTGRES_DBNAME"],
|
|
101
101
|
os.environ["POSTGRES_HOST"],
|
|
102
102
|
os.environ["POSTGRES_PORT"],
|
|
103
103
|
os.environ["POSTGRES_USER"],
|
|
104
104
|
os.environ["POSTGRES_PASSWORD"],
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
)
|
|
106
|
+
drop_postgres_table(db, "public.searchablecontentapplication_events")
|
|
107
|
+
drop_postgres_table(db, "public.pages_projection_example")
|
|
108
|
+
db.close()
|
|
108
109
|
|
|
109
110
|
|
|
110
111
|
del SearchableContentApplicationTestCase
|
|
@@ -54,15 +54,16 @@ class TestWithPostgres(SearchableContentRecorderTestCase):
|
|
|
54
54
|
super().tearDown()
|
|
55
55
|
|
|
56
56
|
def drop_tables(self) -> None:
|
|
57
|
-
|
|
57
|
+
db = PostgresDatastore(
|
|
58
58
|
os.environ["POSTGRES_DBNAME"],
|
|
59
59
|
os.environ["POSTGRES_HOST"],
|
|
60
60
|
os.environ["POSTGRES_PORT"],
|
|
61
61
|
os.environ["POSTGRES_USER"],
|
|
62
62
|
os.environ["POSTGRES_PASSWORD"],
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
)
|
|
64
|
+
drop_postgres_table(db, "public.searchablecontentapplication_events")
|
|
65
|
+
drop_postgres_table(db, "public.pages_projection_example")
|
|
66
|
+
db.close()
|
|
66
67
|
|
|
67
68
|
|
|
68
69
|
del SearchableContentRecorderTestCase
|
|
@@ -76,19 +76,16 @@ class WithPostgreSQL(SearchableTimestampsTestCase):
|
|
|
76
76
|
super().tearDown()
|
|
77
77
|
|
|
78
78
|
def drop_tables(self) -> None:
|
|
79
|
-
|
|
79
|
+
db = PostgresDatastore(
|
|
80
80
|
os.environ["POSTGRES_DBNAME"],
|
|
81
81
|
os.environ["POSTGRES_HOST"],
|
|
82
82
|
os.environ["POSTGRES_PORT"],
|
|
83
83
|
os.environ["POSTGRES_USER"],
|
|
84
84
|
os.environ["POSTGRES_PASSWORD"],
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
drop_postgres_table(
|
|
90
|
-
datastore, "public.searchabletimestampsapplication_timestamps"
|
|
91
|
-
)
|
|
85
|
+
)
|
|
86
|
+
drop_postgres_table(db, "public.searchabletimestampsapplication_events")
|
|
87
|
+
drop_postgres_table(db, "public.searchabletimestampsapplication_timestamps")
|
|
88
|
+
db.close()
|
|
92
89
|
|
|
93
90
|
|
|
94
91
|
del SearchableTimestampsTestCase
|
eventsourcing/postgres.py
CHANGED
|
@@ -2,14 +2,13 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from contextlib import contextmanager
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Callable, Iterator, List, Sequence
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Sequence
|
|
6
6
|
|
|
7
7
|
import psycopg
|
|
8
8
|
import psycopg.errors
|
|
9
9
|
import psycopg_pool
|
|
10
10
|
from psycopg import Connection, Cursor
|
|
11
11
|
from psycopg.rows import DictRow, dict_row
|
|
12
|
-
from typing_extensions import Self
|
|
13
12
|
|
|
14
13
|
from eventsourcing.persistence import (
|
|
15
14
|
AggregateRecorder,
|
|
@@ -63,11 +62,11 @@ class PostgresDatastore:
|
|
|
63
62
|
user: str,
|
|
64
63
|
password: str,
|
|
65
64
|
*,
|
|
66
|
-
connect_timeout: int =
|
|
65
|
+
connect_timeout: int = 5,
|
|
67
66
|
idle_in_transaction_session_timeout: int = 0,
|
|
68
67
|
pool_size: int = 2,
|
|
69
68
|
max_overflow: int = 2,
|
|
70
|
-
|
|
69
|
+
pool_timeout: float = 5.0,
|
|
71
70
|
conn_max_age: float = 60 * 60.0,
|
|
72
71
|
pre_ping: bool = False,
|
|
73
72
|
lock_timeout: int = 0,
|
|
@@ -80,6 +79,7 @@ class PostgresDatastore:
|
|
|
80
79
|
self.pool_open_timeout = pool_open_timeout
|
|
81
80
|
|
|
82
81
|
check = ConnectionPool.check_connection if pre_ping else None
|
|
82
|
+
kwargs: Dict[str, Any] = {"check": check}
|
|
83
83
|
self.pool = ConnectionPool(
|
|
84
84
|
get_password_func=get_password_func,
|
|
85
85
|
connection_class=Connection[DictRow],
|
|
@@ -96,9 +96,9 @@ class PostgresDatastore:
|
|
|
96
96
|
open=False,
|
|
97
97
|
configure=self.after_connect,
|
|
98
98
|
timeout=connect_timeout,
|
|
99
|
-
max_waiting=
|
|
99
|
+
max_waiting=round(pool_timeout),
|
|
100
100
|
max_lifetime=conn_max_age,
|
|
101
|
-
check
|
|
101
|
+
**kwargs, # use the 'check' argument when no longer supporting Python 3.7
|
|
102
102
|
)
|
|
103
103
|
self.lock_timeout = lock_timeout
|
|
104
104
|
self.schema = schema.strip()
|
|
@@ -156,12 +156,6 @@ class PostgresDatastore:
|
|
|
156
156
|
def __del__(self) -> None:
|
|
157
157
|
self.close()
|
|
158
158
|
|
|
159
|
-
def __enter__(self) -> Self:
|
|
160
|
-
return self
|
|
161
|
-
|
|
162
|
-
def __exit__(self, *args: object, **kwargs: Any) -> None:
|
|
163
|
-
self.close()
|
|
164
|
-
|
|
165
159
|
|
|
166
160
|
class PostgresAggregateRecorder(AggregateRecorder):
|
|
167
161
|
def __init__(
|
|
@@ -564,10 +558,10 @@ class Factory(InfrastructureFactory):
|
|
|
564
558
|
POSTGRES_CONNECT_TIMEOUT = "POSTGRES_CONNECT_TIMEOUT"
|
|
565
559
|
POSTGRES_CONN_MAX_AGE = "POSTGRES_CONN_MAX_AGE"
|
|
566
560
|
POSTGRES_PRE_PING = "POSTGRES_PRE_PING"
|
|
567
|
-
|
|
561
|
+
POSTGRES_POOL_TIMEOUT = "POSTGRES_POOL_TIMEOUT"
|
|
568
562
|
POSTGRES_LOCK_TIMEOUT = "POSTGRES_LOCK_TIMEOUT"
|
|
569
563
|
POSTGRES_POOL_SIZE = "POSTGRES_POOL_SIZE"
|
|
570
|
-
|
|
564
|
+
POSTGRES_POOL_MAX_OVERFLOW = "POSTGRES_POOL_MAX_OVERFLOW"
|
|
571
565
|
POSTGRES_IDLE_IN_TRANSACTION_SESSION_TIMEOUT = (
|
|
572
566
|
"POSTGRES_IDLE_IN_TRANSACTION_SESSION_TIMEOUT"
|
|
573
567
|
)
|
|
@@ -624,7 +618,7 @@ class Factory(InfrastructureFactory):
|
|
|
624
618
|
get_password_func = resolve_topic(get_password_topic)
|
|
625
619
|
password = ""
|
|
626
620
|
|
|
627
|
-
connect_timeout =
|
|
621
|
+
connect_timeout = 5
|
|
628
622
|
connect_timeout_str = self.env.get(self.POSTGRES_CONNECT_TIMEOUT)
|
|
629
623
|
if connect_timeout_str:
|
|
630
624
|
try:
|
|
@@ -670,30 +664,30 @@ class Factory(InfrastructureFactory):
|
|
|
670
664
|
raise OSError(msg) from None
|
|
671
665
|
|
|
672
666
|
pool_max_overflow = 10
|
|
673
|
-
pool_max_overflow_str = self.env.get(self.
|
|
667
|
+
pool_max_overflow_str = self.env.get(self.POSTGRES_POOL_MAX_OVERFLOW)
|
|
674
668
|
if pool_max_overflow_str:
|
|
675
669
|
try:
|
|
676
670
|
pool_max_overflow = int(pool_max_overflow_str)
|
|
677
671
|
except ValueError:
|
|
678
672
|
msg = (
|
|
679
673
|
"Postgres environment value for key "
|
|
680
|
-
f"'{self.
|
|
674
|
+
f"'{self.POSTGRES_POOL_MAX_OVERFLOW}' is invalid. "
|
|
681
675
|
"If set, an integer or empty string is expected: "
|
|
682
676
|
f"'{pool_max_overflow_str}'"
|
|
683
677
|
)
|
|
684
678
|
raise OSError(msg) from None
|
|
685
679
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
if
|
|
680
|
+
pool_timeout = 30.0
|
|
681
|
+
pool_timeout_str = self.env.get(self.POSTGRES_POOL_TIMEOUT)
|
|
682
|
+
if pool_timeout_str:
|
|
689
683
|
try:
|
|
690
|
-
|
|
684
|
+
pool_timeout = float(pool_timeout_str)
|
|
691
685
|
except ValueError:
|
|
692
686
|
msg = (
|
|
693
687
|
"Postgres environment value for key "
|
|
694
|
-
f"'{self.
|
|
695
|
-
"If set,
|
|
696
|
-
f"'{
|
|
688
|
+
f"'{self.POSTGRES_POOL_TIMEOUT}' is invalid. "
|
|
689
|
+
"If set, a float or empty string is expected: "
|
|
690
|
+
f"'{pool_timeout_str}'"
|
|
697
691
|
)
|
|
698
692
|
raise OSError(msg) from None
|
|
699
693
|
|
|
@@ -739,16 +733,13 @@ class Factory(InfrastructureFactory):
|
|
|
739
733
|
idle_in_transaction_session_timeout=idle_in_transaction_session_timeout,
|
|
740
734
|
pool_size=pool_size,
|
|
741
735
|
max_overflow=pool_max_overflow,
|
|
742
|
-
|
|
736
|
+
pool_timeout=pool_timeout,
|
|
743
737
|
conn_max_age=conn_max_age,
|
|
744
738
|
pre_ping=pre_ping,
|
|
745
739
|
lock_timeout=lock_timeout,
|
|
746
740
|
schema=schema,
|
|
747
741
|
)
|
|
748
742
|
|
|
749
|
-
def env_create_table(self) -> bool:
|
|
750
|
-
return strtobool(self.env.get(self.CREATE_TABLE) or "yes")
|
|
751
|
-
|
|
752
743
|
def aggregate_recorder(self, purpose: str = "events") -> AggregateRecorder:
|
|
753
744
|
prefix = self.env.name.lower() or "stored"
|
|
754
745
|
events_table_name = prefix + "_" + purpose
|
|
@@ -792,6 +783,9 @@ class Factory(InfrastructureFactory):
|
|
|
792
783
|
recorder.create_table()
|
|
793
784
|
return recorder
|
|
794
785
|
|
|
786
|
+
def env_create_table(self) -> bool:
|
|
787
|
+
return strtobool(self.env.get(self.CREATE_TABLE) or "yes")
|
|
788
|
+
|
|
795
789
|
def close(self) -> None:
|
|
796
790
|
if hasattr(self, "datastore"):
|
|
797
791
|
self.datastore.close()
|
eventsourcing/system.py
CHANGED
|
@@ -8,7 +8,6 @@ from queue import Full, Queue
|
|
|
8
8
|
from threading import Event, Lock, RLock, Thread
|
|
9
9
|
from types import FrameType, ModuleType
|
|
10
10
|
from typing import (
|
|
11
|
-
Any,
|
|
12
11
|
ClassVar,
|
|
13
12
|
Dict,
|
|
14
13
|
Iterable,
|
|
@@ -22,8 +21,6 @@ from typing import (
|
|
|
22
21
|
cast,
|
|
23
22
|
)
|
|
24
23
|
|
|
25
|
-
from typing_extensions import Self
|
|
26
|
-
|
|
27
24
|
from eventsourcing.application import (
|
|
28
25
|
Application,
|
|
29
26
|
NotificationLog,
|
|
@@ -528,13 +525,6 @@ class SingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
|
528
525
|
assert isinstance(app, cls)
|
|
529
526
|
return app
|
|
530
527
|
|
|
531
|
-
def __enter__(self) -> Self:
|
|
532
|
-
self.start()
|
|
533
|
-
return self
|
|
534
|
-
|
|
535
|
-
def __exit__(self, *args: object, **kwargs: Any) -> None:
|
|
536
|
-
self.stop()
|
|
537
|
-
|
|
538
528
|
|
|
539
529
|
class NewSingleThreadedRunner(Runner, RecordingEventReceiver):
|
|
540
530
|
"""
|