eventsourcing 9.3.3__py3-none-any.whl → 9.3.4__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.

Files changed (127) hide show
  1. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.4.dist-info}/METADATA +1 -1
  2. eventsourcing-9.3.4.dist-info/RECORD +24 -0
  3. eventsourcing/examples/__init__.py +0 -0
  4. eventsourcing/examples/aggregate1/__init__.py +0 -0
  5. eventsourcing/examples/aggregate1/application.py +0 -27
  6. eventsourcing/examples/aggregate1/domainmodel.py +0 -16
  7. eventsourcing/examples/aggregate1/test_application.py +0 -37
  8. eventsourcing/examples/aggregate2/__init__.py +0 -0
  9. eventsourcing/examples/aggregate2/application.py +0 -27
  10. eventsourcing/examples/aggregate2/domainmodel.py +0 -22
  11. eventsourcing/examples/aggregate2/test_application.py +0 -37
  12. eventsourcing/examples/aggregate3/__init__.py +0 -0
  13. eventsourcing/examples/aggregate3/application.py +0 -27
  14. eventsourcing/examples/aggregate3/domainmodel.py +0 -38
  15. eventsourcing/examples/aggregate3/test_application.py +0 -37
  16. eventsourcing/examples/aggregate4/__init__.py +0 -0
  17. eventsourcing/examples/aggregate4/application.py +0 -27
  18. eventsourcing/examples/aggregate4/domainmodel.py +0 -114
  19. eventsourcing/examples/aggregate4/test_application.py +0 -38
  20. eventsourcing/examples/aggregate5/__init__.py +0 -0
  21. eventsourcing/examples/aggregate5/application.py +0 -27
  22. eventsourcing/examples/aggregate5/domainmodel.py +0 -131
  23. eventsourcing/examples/aggregate5/test_application.py +0 -38
  24. eventsourcing/examples/aggregate6/__init__.py +0 -0
  25. eventsourcing/examples/aggregate6/application.py +0 -30
  26. eventsourcing/examples/aggregate6/domainmodel.py +0 -123
  27. eventsourcing/examples/aggregate6/test_application.py +0 -38
  28. eventsourcing/examples/aggregate6a/__init__.py +0 -0
  29. eventsourcing/examples/aggregate6a/application.py +0 -40
  30. eventsourcing/examples/aggregate6a/domainmodel.py +0 -149
  31. eventsourcing/examples/aggregate6a/test_application.py +0 -45
  32. eventsourcing/examples/aggregate7/__init__.py +0 -0
  33. eventsourcing/examples/aggregate7/application.py +0 -53
  34. eventsourcing/examples/aggregate7/domainmodel.py +0 -142
  35. eventsourcing/examples/aggregate7/persistence.py +0 -57
  36. eventsourcing/examples/aggregate7/test_application.py +0 -45
  37. eventsourcing/examples/aggregate7/test_compression_and_encryption.py +0 -45
  38. eventsourcing/examples/aggregate7/test_snapshotting_intervals.py +0 -67
  39. eventsourcing/examples/aggregate7a/__init__.py +0 -0
  40. eventsourcing/examples/aggregate7a/application.py +0 -56
  41. eventsourcing/examples/aggregate7a/domainmodel.py +0 -168
  42. eventsourcing/examples/aggregate7a/test_application.py +0 -46
  43. eventsourcing/examples/aggregate7a/test_compression_and_encryption.py +0 -45
  44. eventsourcing/examples/aggregate8/__init__.py +0 -0
  45. eventsourcing/examples/aggregate8/application.py +0 -47
  46. eventsourcing/examples/aggregate8/domainmodel.py +0 -71
  47. eventsourcing/examples/aggregate8/persistence.py +0 -57
  48. eventsourcing/examples/aggregate8/test_application.py +0 -44
  49. eventsourcing/examples/aggregate8/test_compression_and_encryption.py +0 -44
  50. eventsourcing/examples/aggregate8/test_snapshotting_intervals.py +0 -38
  51. eventsourcing/examples/bankaccounts/__init__.py +0 -0
  52. eventsourcing/examples/bankaccounts/application.py +0 -70
  53. eventsourcing/examples/bankaccounts/domainmodel.py +0 -56
  54. eventsourcing/examples/bankaccounts/test.py +0 -173
  55. eventsourcing/examples/cargoshipping/__init__.py +0 -0
  56. eventsourcing/examples/cargoshipping/application.py +0 -126
  57. eventsourcing/examples/cargoshipping/domainmodel.py +0 -330
  58. eventsourcing/examples/cargoshipping/interface.py +0 -143
  59. eventsourcing/examples/cargoshipping/test.py +0 -231
  60. eventsourcing/examples/contentmanagement/__init__.py +0 -0
  61. eventsourcing/examples/contentmanagement/application.py +0 -118
  62. eventsourcing/examples/contentmanagement/domainmodel.py +0 -69
  63. eventsourcing/examples/contentmanagement/test.py +0 -180
  64. eventsourcing/examples/contentmanagement/utils.py +0 -26
  65. eventsourcing/examples/contentmanagementsystem/__init__.py +0 -0
  66. eventsourcing/examples/contentmanagementsystem/application.py +0 -54
  67. eventsourcing/examples/contentmanagementsystem/postgres.py +0 -17
  68. eventsourcing/examples/contentmanagementsystem/sqlite.py +0 -17
  69. eventsourcing/examples/contentmanagementsystem/system.py +0 -14
  70. eventsourcing/examples/contentmanagementsystem/test_system.py +0 -180
  71. eventsourcing/examples/searchablecontent/__init__.py +0 -0
  72. eventsourcing/examples/searchablecontent/application.py +0 -45
  73. eventsourcing/examples/searchablecontent/persistence.py +0 -23
  74. eventsourcing/examples/searchablecontent/postgres.py +0 -118
  75. eventsourcing/examples/searchablecontent/sqlite.py +0 -136
  76. eventsourcing/examples/searchablecontent/test_application.py +0 -110
  77. eventsourcing/examples/searchablecontent/test_recorder.py +0 -68
  78. eventsourcing/examples/searchabletimestamps/__init__.py +0 -0
  79. eventsourcing/examples/searchabletimestamps/application.py +0 -32
  80. eventsourcing/examples/searchabletimestamps/persistence.py +0 -20
  81. eventsourcing/examples/searchabletimestamps/postgres.py +0 -110
  82. eventsourcing/examples/searchabletimestamps/sqlite.py +0 -99
  83. eventsourcing/examples/searchabletimestamps/test_searchabletimestamps.py +0 -94
  84. eventsourcing/examples/test_invoice.py +0 -176
  85. eventsourcing/examples/test_parking_lot.py +0 -206
  86. eventsourcing/tests/application_tests/__init__.py +0 -0
  87. eventsourcing/tests/application_tests/test_application_with_automatic_snapshotting.py +0 -55
  88. eventsourcing/tests/application_tests/test_application_with_popo.py +0 -22
  89. eventsourcing/tests/application_tests/test_application_with_postgres.py +0 -75
  90. eventsourcing/tests/application_tests/test_application_with_sqlite.py +0 -72
  91. eventsourcing/tests/application_tests/test_cache.py +0 -134
  92. eventsourcing/tests/application_tests/test_event_sourced_log.py +0 -162
  93. eventsourcing/tests/application_tests/test_notificationlog.py +0 -232
  94. eventsourcing/tests/application_tests/test_notificationlogreader.py +0 -126
  95. eventsourcing/tests/application_tests/test_processapplication.py +0 -110
  96. eventsourcing/tests/application_tests/test_processingpolicy.py +0 -109
  97. eventsourcing/tests/application_tests/test_repository.py +0 -504
  98. eventsourcing/tests/application_tests/test_snapshotting.py +0 -68
  99. eventsourcing/tests/application_tests/test_upcasting.py +0 -459
  100. eventsourcing/tests/docs_tests/__init__.py +0 -0
  101. eventsourcing/tests/docs_tests/test_docs.py +0 -293
  102. eventsourcing/tests/domain_tests/__init__.py +0 -0
  103. eventsourcing/tests/domain_tests/test_aggregate.py +0 -1200
  104. eventsourcing/tests/domain_tests/test_aggregate_decorators.py +0 -1604
  105. eventsourcing/tests/domain_tests/test_domainevent.py +0 -80
  106. eventsourcing/tests/interface_tests/__init__.py +0 -0
  107. eventsourcing/tests/interface_tests/test_remotenotificationlog.py +0 -258
  108. eventsourcing/tests/persistence_tests/__init__.py +0 -0
  109. eventsourcing/tests/persistence_tests/test_aes.py +0 -93
  110. eventsourcing/tests/persistence_tests/test_connection_pool.py +0 -722
  111. eventsourcing/tests/persistence_tests/test_eventstore.py +0 -72
  112. eventsourcing/tests/persistence_tests/test_infrastructure_factory.py +0 -21
  113. eventsourcing/tests/persistence_tests/test_mapper.py +0 -113
  114. eventsourcing/tests/persistence_tests/test_noninterleaving_notification_ids.py +0 -69
  115. eventsourcing/tests/persistence_tests/test_popo.py +0 -124
  116. eventsourcing/tests/persistence_tests/test_postgres.py +0 -1120
  117. eventsourcing/tests/persistence_tests/test_sqlite.py +0 -348
  118. eventsourcing/tests/persistence_tests/test_transcoder.py +0 -44
  119. eventsourcing/tests/system_tests/__init__.py +0 -0
  120. eventsourcing/tests/system_tests/test_runner.py +0 -935
  121. eventsourcing/tests/system_tests/test_system.py +0 -284
  122. eventsourcing/tests/utils_tests/__init__.py +0 -0
  123. eventsourcing/tests/utils_tests/test_utils.py +0 -226
  124. eventsourcing-9.3.3.dist-info/RECORD +0 -145
  125. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.4.dist-info}/AUTHORS +0 -0
  126. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.4.dist-info}/LICENSE +0 -0
  127. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.4.dist-info}/WHEEL +0 -0
@@ -1,99 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from datetime import datetime
4
- from typing import TYPE_CHECKING, Any, List, Sequence, Tuple, cast
5
- from uuid import UUID
6
-
7
- from eventsourcing.domain import Aggregate
8
- from eventsourcing.examples.searchabletimestamps.persistence import (
9
- SearchableTimestampsRecorder,
10
- )
11
- from eventsourcing.sqlite import (
12
- Factory,
13
- SQLiteApplicationRecorder,
14
- SQLiteCursor,
15
- SQLiteDatastore,
16
- )
17
-
18
- if TYPE_CHECKING: # pragma: nocover
19
- from eventsourcing.persistence import ApplicationRecorder, StoredEvent
20
-
21
-
22
- class SearchableTimestampsApplicationRecorder(
23
- SearchableTimestampsRecorder, SQLiteApplicationRecorder
24
- ):
25
- def __init__(
26
- self,
27
- datastore: SQLiteDatastore,
28
- events_table_name: str = "stored_events",
29
- event_timestamps_table_name: str = "event_timestamps",
30
- ):
31
- self.event_timestamps_table_name = event_timestamps_table_name
32
- super().__init__(datastore, events_table_name)
33
- self.insert_event_timestamp_statement = (
34
- f"INSERT INTO {self.event_timestamps_table_name} VALUES (?, ?, ?)"
35
- )
36
- self.select_event_timestamp_statement = (
37
- f"SELECT originator_version FROM {self.event_timestamps_table_name} WHERE "
38
- "originator_id = ? AND "
39
- "timestamp <= ? "
40
- "ORDER BY originator_version DESC "
41
- "LIMIT 1"
42
- )
43
-
44
- def construct_create_table_statements(self) -> List[str]:
45
- statements = super().construct_create_table_statements()
46
- statements.append(
47
- "CREATE TABLE IF NOT EXISTS "
48
- f"{self.event_timestamps_table_name} ("
49
- "originator_id TEXT, "
50
- "timestamp timestamp, "
51
- "originator_version INTEGER, "
52
- "PRIMARY KEY "
53
- "(originator_id, timestamp))"
54
- )
55
- return statements
56
-
57
- def _insert_events(
58
- self,
59
- c: SQLiteCursor,
60
- stored_events: List[StoredEvent],
61
- **kwargs: Any,
62
- ) -> Sequence[int] | None:
63
- notification_ids = super()._insert_events(c, stored_events, **kwargs)
64
-
65
- # Insert event timestamps.
66
- event_timestamps_data = cast(
67
- List[Tuple[UUID, datetime, int]], kwargs["event_timestamps_data"]
68
- )
69
- for originator_id, timestamp, originator_version in event_timestamps_data:
70
- c.execute(
71
- self.insert_event_timestamp_statement,
72
- (originator_id.hex, timestamp, originator_version),
73
- )
74
-
75
- return notification_ids
76
-
77
- def get_version_at_timestamp(
78
- self, originator_id: UUID, timestamp: datetime
79
- ) -> int | None:
80
- with self.datastore.transaction(commit=False) as c:
81
- c.execute(
82
- self.select_event_timestamp_statement, (originator_id.hex, timestamp)
83
- )
84
- for row in c.fetchall():
85
- version = row["originator_version"]
86
- break
87
- else:
88
- version = Aggregate.INITIAL_VERSION - 1
89
- return version
90
-
91
-
92
- class SearchableTimestampsInfrastructureFactory(Factory):
93
- def application_recorder(self) -> ApplicationRecorder:
94
- recorder = SearchableTimestampsApplicationRecorder(datastore=self.datastore)
95
- recorder.create_table()
96
- return recorder
97
-
98
-
99
- del Factory
@@ -1,94 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- from datetime import timedelta
5
- from time import sleep
6
- from typing import ClassVar, Dict
7
- from unittest import TestCase
8
-
9
- from eventsourcing.application import AggregateNotFoundError
10
- from eventsourcing.domain import create_utc_datetime_now
11
- from eventsourcing.examples.cargoshipping.domainmodel import Location
12
- from eventsourcing.examples.searchabletimestamps.application import (
13
- SearchableTimestampsApplication,
14
- )
15
- from eventsourcing.postgres import PostgresDatastore
16
- from eventsourcing.tests.postgres_utils import drop_postgres_table
17
-
18
-
19
- class SearchableTimestampsTestCase(TestCase):
20
- env: ClassVar[Dict[str, str]]
21
-
22
- def test(self) -> None:
23
- # Construct application.
24
- app = SearchableTimestampsApplication(env=self.env)
25
- timestamp0 = create_utc_datetime_now()
26
- sleep(1e-5)
27
-
28
- # Book new cargo.
29
- tracking_id = app.book_new_cargo(
30
- origin=Location["NLRTM"],
31
- destination=Location["USDAL"],
32
- arrival_deadline=create_utc_datetime_now() + timedelta(weeks=3),
33
- )
34
- timestamp1 = create_utc_datetime_now()
35
- sleep(1e-5)
36
-
37
- # Change destination.
38
- app.change_destination(tracking_id, destination=Location["AUMEL"])
39
- timestamp2 = create_utc_datetime_now()
40
- sleep(1e-5)
41
-
42
- # View the state of the cargo tracking at particular times.
43
- with self.assertRaises(AggregateNotFoundError):
44
- app.get_cargo_at_timestamp(tracking_id, timestamp0)
45
-
46
- cargo_at_timestamp1 = app.get_cargo_at_timestamp(tracking_id, timestamp1)
47
- self.assertEqual(cargo_at_timestamp1.destination, Location["USDAL"])
48
-
49
- cargo_at_timestamp2 = app.get_cargo_at_timestamp(tracking_id, timestamp2)
50
- self.assertEqual(cargo_at_timestamp2.destination, Location["AUMEL"])
51
-
52
-
53
- class WithSQLite(SearchableTimestampsTestCase):
54
- env: ClassVar[Dict[str, str]] = {
55
- "PERSISTENCE_MODULE": "eventsourcing.examples.searchabletimestamps.sqlite",
56
- "SQLITE_DBNAME": ":memory:",
57
- }
58
-
59
-
60
- class WithPostgreSQL(SearchableTimestampsTestCase):
61
- env: ClassVar[Dict[str, str]] = {
62
- "PERSISTENCE_MODULE": "eventsourcing.examples.searchabletimestamps.postgres"
63
- }
64
-
65
- def setUp(self) -> None:
66
- super().setUp()
67
- os.environ["POSTGRES_DBNAME"] = "eventsourcing"
68
- os.environ["POSTGRES_HOST"] = "127.0.0.1"
69
- os.environ["POSTGRES_PORT"] = "5432"
70
- os.environ["POSTGRES_USER"] = "eventsourcing"
71
- os.environ["POSTGRES_PASSWORD"] = "eventsourcing" # noqa: S105
72
- self.drop_tables()
73
-
74
- def tearDown(self) -> None:
75
- self.drop_tables()
76
- super().tearDown()
77
-
78
- def drop_tables(self) -> None:
79
- with PostgresDatastore(
80
- os.environ["POSTGRES_DBNAME"],
81
- os.environ["POSTGRES_HOST"],
82
- os.environ["POSTGRES_PORT"],
83
- os.environ["POSTGRES_USER"],
84
- os.environ["POSTGRES_PASSWORD"],
85
- ) as datastore:
86
- drop_postgres_table(
87
- datastore, "public.searchabletimestampsapplication_events"
88
- )
89
- drop_postgres_table(
90
- datastore, "public.searchabletimestampsapplication_timestamps"
91
- )
92
-
93
-
94
- del SearchableTimestampsTestCase
@@ -1,176 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from decimal import Decimal
5
- from enum import Enum
6
- from typing import TYPE_CHECKING, Any, Dict, cast
7
- from unittest import TestCase
8
-
9
- from eventsourcing.application import Application
10
- from eventsourcing.domain import Aggregate, Snapshot, event
11
- from eventsourcing.persistence import Transcoding
12
-
13
- if TYPE_CHECKING: # pragma: nocover
14
- from datetime import datetime
15
-
16
-
17
- class Status(Enum):
18
- INITIATED = 1
19
- ISSUED = 2
20
- SENT = 3
21
-
22
-
23
- class SendMethod(Enum):
24
- EMAIL = 1
25
- POST = 2
26
-
27
-
28
- @dataclass
29
- class Person:
30
- name: str
31
- address: str
32
-
33
-
34
- class Invoice(Aggregate):
35
- @event("Initiated")
36
- def __init__(
37
- self,
38
- number: str,
39
- amount: Decimal,
40
- issued_to: Person,
41
- timestamp: datetime | Any = None,
42
- ):
43
- self._number = number
44
- self._amount = amount
45
- self.issued_to = issued_to
46
- self.initiated_at = timestamp
47
- self.status = Status.INITIATED
48
-
49
- def _get_number(self) -> str:
50
- return self._number
51
-
52
- @event
53
- def _number_updated(self, value: str) -> None:
54
- assert self.status == Status.INITIATED
55
- self._number = value
56
-
57
- number = property(_get_number, _number_updated)
58
-
59
- def _get_amount(self) -> Decimal:
60
- return self._amount
61
-
62
- @event("AmountUpdated")
63
- def _set_amount(self, value: Decimal) -> None:
64
- assert self.status == Status.INITIATED
65
- self._amount = value
66
-
67
- amount = property(_get_amount, _set_amount)
68
-
69
- @event("Issued")
70
- def issue(self, issued_by: str, timestamp: datetime | Any = None) -> None:
71
- self.issued_by = issued_by
72
- self.issued_on = timestamp
73
- self.status = Status.ISSUED
74
-
75
- @event("Sent")
76
- def send(self, sent_via: SendMethod, timestamp: datetime | Any = None) -> None:
77
- self.sent_via = sent_via
78
- self.sent_at = timestamp
79
- self.status = Status.SENT
80
-
81
-
82
- class PersonAsDict(Transcoding):
83
- name = "person_as_dict"
84
- type = Person
85
-
86
- def encode(self, obj: Person) -> Dict[str, Any]:
87
- return obj.__dict__
88
-
89
- def decode(self, data: Dict[str, Any]) -> Person:
90
- return Person(**data)
91
-
92
-
93
- class SendMethodAsStr(Transcoding):
94
- name = "send_method_str"
95
- type = SendMethod
96
-
97
- def encode(self, obj: SendMethod) -> str:
98
- return obj.name
99
-
100
- def decode(self, data: str) -> SendMethod:
101
- return getattr(SendMethod, data)
102
-
103
-
104
- class StatusAsStr(Transcoding):
105
- name = "status_str"
106
- type = Status
107
-
108
- def encode(self, obj: Status) -> str:
109
- return obj.name
110
-
111
- def decode(self, data: str) -> Status:
112
- return getattr(Status, data)
113
-
114
-
115
- class TestInvoice(TestCase):
116
- def test(self) -> None:
117
- invoice = Invoice(
118
- number="INV/2021/11/01",
119
- amount=Decimal("34.20"),
120
- issued_to=Person("Oscar the Grouch", "123 Sesame Street"),
121
- )
122
- self.assertEqual(invoice.number, "INV/2021/11/01")
123
- self.assertEqual(invoice.amount, Decimal("34.20"))
124
- self.assertEqual(
125
- invoice.issued_to, Person("Oscar the Grouch", "123 Sesame Street")
126
- )
127
- self.assertEqual(invoice.status, Status.INITIATED)
128
-
129
- invoice.number = "INV/2021/11/02"
130
- self.assertEqual(invoice.number, "INV/2021/11/02")
131
-
132
- invoice.amount = Decimal("43.20")
133
- self.assertEqual(invoice.number, "INV/2021/11/02")
134
-
135
- invoice.issue(issued_by="Cookie Monster")
136
- self.assertEqual(invoice.issued_by, "Cookie Monster")
137
- self.assertEqual(invoice.status, Status.ISSUED)
138
-
139
- with self.assertRaises(AssertionError):
140
- invoice.number = "INV/2021/11/03"
141
-
142
- with self.assertRaises(AssertionError):
143
- invoice.amount = Decimal("54.20")
144
-
145
- invoice.send(sent_via=SendMethod.EMAIL)
146
- self.assertEqual(invoice.sent_via, SendMethod.EMAIL)
147
- self.assertEqual(invoice.status, Status.SENT)
148
-
149
- app: Application = Application(env={"IS_SNAPSHOTTING_ENABLED": "y"})
150
- app.mapper.transcoder.register(PersonAsDict())
151
- app.mapper.transcoder.register(SendMethodAsStr())
152
- app.mapper.transcoder.register(StatusAsStr())
153
-
154
- app.save(invoice)
155
-
156
- copy: Invoice = app.repository.get(invoice.id)
157
- self.assertEqual(invoice, copy)
158
-
159
- assert app.snapshots is not None
160
- snapshots = list(app.snapshots.get(invoice.id))
161
- self.assertEqual(len(snapshots), 0)
162
-
163
- app.take_snapshot(invoice.id)
164
-
165
- copy = app.repository.get(invoice.id)
166
- self.assertEqual(invoice, copy)
167
-
168
- copy = app.repository.get(invoice.id, version=1)
169
- self.assertNotEqual(invoice, copy)
170
-
171
- snapshots = list(app.snapshots.get(invoice.id))
172
- self.assertEqual(len(snapshots), 1)
173
-
174
- snapshot = cast(Snapshot, snapshots[0])
175
- copy2 = cast(Invoice, snapshot.mutate(None))
176
- self.assertEqual(invoice, copy2)
@@ -1,206 +0,0 @@
1
- """
2
- After Ed Blackburn's https://github.com/edblackburn/parking-lot/.
3
- """
4
-
5
- from __future__ import annotations
6
-
7
- import re
8
- from dataclasses import dataclass
9
- from datetime import datetime, timedelta
10
- from typing import List, Type, cast
11
- from unittest import TestCase
12
- from uuid import NAMESPACE_URL, UUID, uuid5
13
-
14
- from eventsourcing.application import AggregateNotFoundError, Application
15
- from eventsourcing.domain import Aggregate, triggers
16
- from eventsourcing.system import NotificationLogReader
17
-
18
-
19
- @dataclass
20
- class LicencePlate:
21
- number: str
22
- regex = re.compile("^[0-9]{3}-[0-9]{3}$")
23
-
24
- def __post_init__(self) -> None:
25
- if not bool(self.regex.match(self.number)):
26
- raise ValueError
27
-
28
-
29
- @dataclass
30
- class Booking:
31
- start: datetime
32
- finish: datetime
33
-
34
-
35
- class Product:
36
- delta = timedelta()
37
-
38
- @classmethod
39
- def calc_finish(cls, start: datetime) -> datetime:
40
- return start + cls.delta
41
-
42
-
43
- class EndOfDay(Product):
44
- delta = timedelta(days=1, seconds=-1)
45
-
46
-
47
- class EndOfWeek(Product):
48
- delta = timedelta(days=7, seconds=-1)
49
-
50
-
51
- class Vehicle(Aggregate):
52
- class Event(Aggregate.Event):
53
- pass
54
-
55
- class Registered(Event, Aggregate.Created):
56
- licence_plate_number: str
57
-
58
- class Booked(Event):
59
- start: datetime
60
- finish: datetime
61
-
62
- class Unbooked(Event):
63
- when: datetime
64
-
65
- @triggers(Registered)
66
- def __init__(self, licence_plate_number: str):
67
- self.licence_plate_number = licence_plate_number
68
- self.bookings: List[Booking] = []
69
- self.inspection_failures: List[datetime] = []
70
-
71
- @triggers(Booked)
72
- def book(self, start: datetime, finish: datetime) -> None:
73
- self.bookings.append(Booking(start, finish))
74
-
75
- @triggers(Unbooked)
76
- def fail_inspection(self, when: datetime) -> None:
77
- self.inspection_failures.append(when)
78
-
79
- @property
80
- def licence_plate(self) -> LicencePlate:
81
- return LicencePlate(self.licence_plate_number)
82
-
83
- def inspect(self, when: datetime) -> None:
84
- for booking in self.bookings:
85
- if booking.start < when < booking.finish:
86
- break
87
- else:
88
- self.fail_inspection(when)
89
-
90
- @staticmethod
91
- def create_id(licence_plate_number: str) -> UUID:
92
- return uuid5(NAMESPACE_URL, f"/licence_plate_numbers/{licence_plate_number}")
93
-
94
-
95
- class ParkingLot(Application):
96
- def book(self, licence_plate: LicencePlate, product: Type[Product]) -> None:
97
- try:
98
- vehicle = self.get_vehicle(licence_plate)
99
- except AggregateNotFoundError:
100
- vehicle = Vehicle(licence_plate.number)
101
- start = Vehicle.Event.create_timestamp()
102
- finish = product.calc_finish(start)
103
- vehicle.book(start=start, finish=finish)
104
- self.save(vehicle)
105
-
106
- def inspect(self, licence_plate: LicencePlate, when: datetime) -> None:
107
- vehicle = self.get_vehicle(licence_plate)
108
- vehicle.inspect(when)
109
- self.save(vehicle)
110
-
111
- def get_vehicle(self, licence_plate: LicencePlate) -> Vehicle:
112
- return cast(
113
- Vehicle, self.repository.get(Vehicle.create_id(licence_plate.number))
114
- )
115
-
116
-
117
- class TestParkingLot(TestCase):
118
- def test_licence_plate(self) -> None:
119
- # Valid.
120
- licence_plate = LicencePlate("123-123")
121
- self.assertIsInstance(licence_plate, LicencePlate)
122
- self.assertEqual(licence_plate.number, "123-123")
123
-
124
- # Invalid.
125
- with self.assertRaises(ValueError):
126
- LicencePlate("abcdef")
127
-
128
- def test_parking_lot(self) -> None:
129
- # Construct the application object to use an SQLite database.
130
- app = ParkingLot(
131
- env={
132
- "PERSISTENCE_MODULE": "eventsourcing.sqlite",
133
- "SQLITE_DBNAME": ":memory:",
134
- }
135
- )
136
-
137
- # Create a valid licence plate.
138
- licence_plate = LicencePlate("123-123")
139
-
140
- # Book unregistered vehicle.
141
- app.book(licence_plate, EndOfDay)
142
-
143
- # Check vehicle state.
144
- vehicle = app.get_vehicle(licence_plate)
145
- self.assertEqual(vehicle.licence_plate, licence_plate)
146
- self.assertEqual(len(vehicle.bookings), 1)
147
- self.assertEqual(len(vehicle.inspection_failures), 0)
148
- booking1 = vehicle.bookings[-1]
149
-
150
- # Book registered vehicle.
151
- app.book(licence_plate, EndOfWeek)
152
-
153
- # Check vehicle state.
154
- vehicle = app.get_vehicle(licence_plate)
155
- self.assertEqual(len(vehicle.bookings), 2)
156
- self.assertEqual(len(vehicle.inspection_failures), 0)
157
- booking2 = vehicle.bookings[-1]
158
-
159
- # Inspect whilst has booking.
160
- app.inspect(licence_plate, Vehicle.Event.create_timestamp())
161
-
162
- # Check vehicle state.
163
- vehicle = app.get_vehicle(licence_plate)
164
- self.assertEqual(len(vehicle.bookings), 2)
165
- self.assertEqual(len(vehicle.inspection_failures), 0)
166
-
167
- # Inspect after bookings expired.
168
- inspected_on = Vehicle.Event.create_timestamp() + timedelta(days=10)
169
- app.inspect(licence_plate, inspected_on)
170
-
171
- # Check vehicle state.
172
- vehicle = app.get_vehicle(licence_plate)
173
- self.assertEqual(len(vehicle.bookings), 2)
174
- self.assertEqual(len(vehicle.inspection_failures), 1)
175
-
176
- # Check all domain events in bounded context.
177
- notifications = NotificationLogReader(app.notification_log).read(start=1)
178
- domain_events = [app.mapper.to_domain_event(n) for n in notifications]
179
- self.assertEqual(len(domain_events), 4)
180
-
181
- vehicle1_id = Vehicle.create_id("123-123")
182
- event0 = domain_events[0]
183
- assert isinstance(event0, Vehicle.Registered)
184
- self.assertEqual(event0.originator_id, vehicle1_id)
185
- self.assertEqual(event0.originator_version, 1)
186
- self.assertEqual(event0.licence_plate_number, "123-123")
187
-
188
- event1 = domain_events[1]
189
- assert isinstance(event1, Vehicle.Booked)
190
- self.assertEqual(event1.originator_id, vehicle1_id)
191
- self.assertEqual(event1.originator_version, 2)
192
- self.assertEqual(event1.start, booking1.start)
193
- self.assertEqual(event1.finish, booking1.finish)
194
-
195
- event2 = domain_events[2]
196
- assert isinstance(event2, Vehicle.Booked)
197
- self.assertEqual(event2.originator_id, vehicle1_id)
198
- self.assertEqual(event2.originator_version, 3)
199
- self.assertEqual(event2.start, booking2.start)
200
- self.assertEqual(event2.finish, booking2.finish)
201
-
202
- event3 = domain_events[3]
203
- assert isinstance(event3, Vehicle.Unbooked)
204
- self.assertEqual(event3.originator_id, vehicle1_id)
205
- self.assertEqual(event3.originator_version, 4)
206
- self.assertEqual(event3.when, inspected_on)
File without changes
@@ -1,55 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from decimal import Decimal
4
- from typing import ClassVar, Dict, Type
5
- from unittest import TestCase
6
-
7
- from eventsourcing.domain import Aggregate, MutableOrImmutableAggregate
8
- from eventsourcing.tests.application import BankAccounts
9
- from eventsourcing.tests.domain import BankAccount
10
-
11
-
12
- class BankAccountsWithAutomaticSnapshotting(BankAccounts):
13
- is_snapshotting_enabled = False
14
- snapshotting_intervals: ClassVar[
15
- Dict[Type[MutableOrImmutableAggregate], int] | None
16
- ] = {BankAccount: 5}
17
-
18
-
19
- class TestApplicationWithAutomaticSnapshotting(TestCase):
20
- def test(self):
21
- app = BankAccountsWithAutomaticSnapshotting()
22
-
23
- # Check snapshotting is enabled by setting snapshotting_intervals only.
24
- self.assertTrue(app.snapshots)
25
-
26
- # Open an account.
27
- account_id = app.open_account("Alice", "alice@example.com")
28
-
29
- # Check there are no snapshots.
30
- snapshots = list(app.snapshots.get(account_id))
31
- self.assertEqual(len(snapshots), 0)
32
-
33
- # Trigger twelve more events.
34
- for _ in range(12):
35
- app.credit_account(account_id, Decimal("10.00"))
36
-
37
- # Check the account is at version 13.
38
- account = app.get_account(account_id)
39
- self.assertEqual(account.version, 13)
40
-
41
- # Check snapshots have been taken at regular intervals.
42
- snapshots = list(app.snapshots.get(account_id))
43
- self.assertEqual(len(snapshots), 2)
44
- self.assertEqual(snapshots[0].originator_version, 5)
45
- self.assertEqual(snapshots[1].originator_version, 10)
46
-
47
- # Check another type of aggregate is not snapshotted.
48
- aggregate = Aggregate()
49
- for _ in range(10):
50
- aggregate.trigger_event(Aggregate.Event)
51
- app.save(aggregate)
52
-
53
- # Check snapshots have not been taken at regular intervals.
54
- snapshots = list(app.snapshots.get(aggregate.id))
55
- self.assertEqual(len(snapshots), 0)
@@ -1,22 +0,0 @@
1
- from eventsourcing.tests.application import (
2
- TIMEIT_FACTOR,
3
- ApplicationTestCase,
4
- ExampleApplicationTestCase,
5
- )
6
-
7
-
8
- class TestApplicationWithPOPO(ApplicationTestCase):
9
- def test_application_fastforward_skipping_during_contention(self):
10
- self.skipTest("POPO is too fast for this test to work")
11
-
12
- def test_application_fastforward_blocking_during_contention(self):
13
- self.skipTest("POPO is too fast for this test to make sense")
14
-
15
-
16
- class TestExampleApplicationWithPOPO(ExampleApplicationTestCase):
17
- timeit_number = 100 * TIMEIT_FACTOR
18
- expected_factory_topic = "eventsourcing.popo:Factory"
19
-
20
-
21
- del ApplicationTestCase
22
- del ExampleApplicationTestCase