eventsourcing 9.3.3__py3-none-any.whl → 9.3.5__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 (129) hide show
  1. eventsourcing/application.py +0 -15
  2. eventsourcing/system.py +25 -2
  3. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.5.dist-info}/METADATA +1 -1
  4. eventsourcing-9.3.5.dist-info/RECORD +24 -0
  5. eventsourcing/examples/__init__.py +0 -0
  6. eventsourcing/examples/aggregate1/__init__.py +0 -0
  7. eventsourcing/examples/aggregate1/application.py +0 -27
  8. eventsourcing/examples/aggregate1/domainmodel.py +0 -16
  9. eventsourcing/examples/aggregate1/test_application.py +0 -37
  10. eventsourcing/examples/aggregate2/__init__.py +0 -0
  11. eventsourcing/examples/aggregate2/application.py +0 -27
  12. eventsourcing/examples/aggregate2/domainmodel.py +0 -22
  13. eventsourcing/examples/aggregate2/test_application.py +0 -37
  14. eventsourcing/examples/aggregate3/__init__.py +0 -0
  15. eventsourcing/examples/aggregate3/application.py +0 -27
  16. eventsourcing/examples/aggregate3/domainmodel.py +0 -38
  17. eventsourcing/examples/aggregate3/test_application.py +0 -37
  18. eventsourcing/examples/aggregate4/__init__.py +0 -0
  19. eventsourcing/examples/aggregate4/application.py +0 -27
  20. eventsourcing/examples/aggregate4/domainmodel.py +0 -114
  21. eventsourcing/examples/aggregate4/test_application.py +0 -38
  22. eventsourcing/examples/aggregate5/__init__.py +0 -0
  23. eventsourcing/examples/aggregate5/application.py +0 -27
  24. eventsourcing/examples/aggregate5/domainmodel.py +0 -131
  25. eventsourcing/examples/aggregate5/test_application.py +0 -38
  26. eventsourcing/examples/aggregate6/__init__.py +0 -0
  27. eventsourcing/examples/aggregate6/application.py +0 -30
  28. eventsourcing/examples/aggregate6/domainmodel.py +0 -123
  29. eventsourcing/examples/aggregate6/test_application.py +0 -38
  30. eventsourcing/examples/aggregate6a/__init__.py +0 -0
  31. eventsourcing/examples/aggregate6a/application.py +0 -40
  32. eventsourcing/examples/aggregate6a/domainmodel.py +0 -149
  33. eventsourcing/examples/aggregate6a/test_application.py +0 -45
  34. eventsourcing/examples/aggregate7/__init__.py +0 -0
  35. eventsourcing/examples/aggregate7/application.py +0 -53
  36. eventsourcing/examples/aggregate7/domainmodel.py +0 -142
  37. eventsourcing/examples/aggregate7/persistence.py +0 -57
  38. eventsourcing/examples/aggregate7/test_application.py +0 -45
  39. eventsourcing/examples/aggregate7/test_compression_and_encryption.py +0 -45
  40. eventsourcing/examples/aggregate7/test_snapshotting_intervals.py +0 -67
  41. eventsourcing/examples/aggregate7a/__init__.py +0 -0
  42. eventsourcing/examples/aggregate7a/application.py +0 -56
  43. eventsourcing/examples/aggregate7a/domainmodel.py +0 -168
  44. eventsourcing/examples/aggregate7a/test_application.py +0 -46
  45. eventsourcing/examples/aggregate7a/test_compression_and_encryption.py +0 -45
  46. eventsourcing/examples/aggregate8/__init__.py +0 -0
  47. eventsourcing/examples/aggregate8/application.py +0 -47
  48. eventsourcing/examples/aggregate8/domainmodel.py +0 -71
  49. eventsourcing/examples/aggregate8/persistence.py +0 -57
  50. eventsourcing/examples/aggregate8/test_application.py +0 -44
  51. eventsourcing/examples/aggregate8/test_compression_and_encryption.py +0 -44
  52. eventsourcing/examples/aggregate8/test_snapshotting_intervals.py +0 -38
  53. eventsourcing/examples/bankaccounts/__init__.py +0 -0
  54. eventsourcing/examples/bankaccounts/application.py +0 -70
  55. eventsourcing/examples/bankaccounts/domainmodel.py +0 -56
  56. eventsourcing/examples/bankaccounts/test.py +0 -173
  57. eventsourcing/examples/cargoshipping/__init__.py +0 -0
  58. eventsourcing/examples/cargoshipping/application.py +0 -126
  59. eventsourcing/examples/cargoshipping/domainmodel.py +0 -330
  60. eventsourcing/examples/cargoshipping/interface.py +0 -143
  61. eventsourcing/examples/cargoshipping/test.py +0 -231
  62. eventsourcing/examples/contentmanagement/__init__.py +0 -0
  63. eventsourcing/examples/contentmanagement/application.py +0 -118
  64. eventsourcing/examples/contentmanagement/domainmodel.py +0 -69
  65. eventsourcing/examples/contentmanagement/test.py +0 -180
  66. eventsourcing/examples/contentmanagement/utils.py +0 -26
  67. eventsourcing/examples/contentmanagementsystem/__init__.py +0 -0
  68. eventsourcing/examples/contentmanagementsystem/application.py +0 -54
  69. eventsourcing/examples/contentmanagementsystem/postgres.py +0 -17
  70. eventsourcing/examples/contentmanagementsystem/sqlite.py +0 -17
  71. eventsourcing/examples/contentmanagementsystem/system.py +0 -14
  72. eventsourcing/examples/contentmanagementsystem/test_system.py +0 -180
  73. eventsourcing/examples/searchablecontent/__init__.py +0 -0
  74. eventsourcing/examples/searchablecontent/application.py +0 -45
  75. eventsourcing/examples/searchablecontent/persistence.py +0 -23
  76. eventsourcing/examples/searchablecontent/postgres.py +0 -118
  77. eventsourcing/examples/searchablecontent/sqlite.py +0 -136
  78. eventsourcing/examples/searchablecontent/test_application.py +0 -110
  79. eventsourcing/examples/searchablecontent/test_recorder.py +0 -68
  80. eventsourcing/examples/searchabletimestamps/__init__.py +0 -0
  81. eventsourcing/examples/searchabletimestamps/application.py +0 -32
  82. eventsourcing/examples/searchabletimestamps/persistence.py +0 -20
  83. eventsourcing/examples/searchabletimestamps/postgres.py +0 -110
  84. eventsourcing/examples/searchabletimestamps/sqlite.py +0 -99
  85. eventsourcing/examples/searchabletimestamps/test_searchabletimestamps.py +0 -94
  86. eventsourcing/examples/test_invoice.py +0 -176
  87. eventsourcing/examples/test_parking_lot.py +0 -206
  88. eventsourcing/tests/application_tests/__init__.py +0 -0
  89. eventsourcing/tests/application_tests/test_application_with_automatic_snapshotting.py +0 -55
  90. eventsourcing/tests/application_tests/test_application_with_popo.py +0 -22
  91. eventsourcing/tests/application_tests/test_application_with_postgres.py +0 -75
  92. eventsourcing/tests/application_tests/test_application_with_sqlite.py +0 -72
  93. eventsourcing/tests/application_tests/test_cache.py +0 -134
  94. eventsourcing/tests/application_tests/test_event_sourced_log.py +0 -162
  95. eventsourcing/tests/application_tests/test_notificationlog.py +0 -232
  96. eventsourcing/tests/application_tests/test_notificationlogreader.py +0 -126
  97. eventsourcing/tests/application_tests/test_processapplication.py +0 -110
  98. eventsourcing/tests/application_tests/test_processingpolicy.py +0 -109
  99. eventsourcing/tests/application_tests/test_repository.py +0 -504
  100. eventsourcing/tests/application_tests/test_snapshotting.py +0 -68
  101. eventsourcing/tests/application_tests/test_upcasting.py +0 -459
  102. eventsourcing/tests/docs_tests/__init__.py +0 -0
  103. eventsourcing/tests/docs_tests/test_docs.py +0 -293
  104. eventsourcing/tests/domain_tests/__init__.py +0 -0
  105. eventsourcing/tests/domain_tests/test_aggregate.py +0 -1200
  106. eventsourcing/tests/domain_tests/test_aggregate_decorators.py +0 -1604
  107. eventsourcing/tests/domain_tests/test_domainevent.py +0 -80
  108. eventsourcing/tests/interface_tests/__init__.py +0 -0
  109. eventsourcing/tests/interface_tests/test_remotenotificationlog.py +0 -258
  110. eventsourcing/tests/persistence_tests/__init__.py +0 -0
  111. eventsourcing/tests/persistence_tests/test_aes.py +0 -93
  112. eventsourcing/tests/persistence_tests/test_connection_pool.py +0 -722
  113. eventsourcing/tests/persistence_tests/test_eventstore.py +0 -72
  114. eventsourcing/tests/persistence_tests/test_infrastructure_factory.py +0 -21
  115. eventsourcing/tests/persistence_tests/test_mapper.py +0 -113
  116. eventsourcing/tests/persistence_tests/test_noninterleaving_notification_ids.py +0 -69
  117. eventsourcing/tests/persistence_tests/test_popo.py +0 -124
  118. eventsourcing/tests/persistence_tests/test_postgres.py +0 -1120
  119. eventsourcing/tests/persistence_tests/test_sqlite.py +0 -348
  120. eventsourcing/tests/persistence_tests/test_transcoder.py +0 -44
  121. eventsourcing/tests/system_tests/__init__.py +0 -0
  122. eventsourcing/tests/system_tests/test_runner.py +0 -935
  123. eventsourcing/tests/system_tests/test_system.py +0 -284
  124. eventsourcing/tests/utils_tests/__init__.py +0 -0
  125. eventsourcing/tests/utils_tests/test_utils.py +0 -226
  126. eventsourcing-9.3.3.dist-info/RECORD +0 -145
  127. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.5.dist-info}/AUTHORS +0 -0
  128. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.5.dist-info}/LICENSE +0 -0
  129. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.5.dist-info}/WHEEL +0 -0
@@ -1,68 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- from typing import ClassVar, Dict, cast
5
- from unittest import TestCase
6
- from uuid import uuid4
7
-
8
- from eventsourcing.examples.contentmanagement.application import PageNotFoundError
9
- from eventsourcing.examples.searchablecontent.application import (
10
- SearchableContentApplication,
11
- )
12
- from eventsourcing.examples.searchablecontent.persistence import (
13
- SearchableContentRecorder,
14
- )
15
- from eventsourcing.postgres import PostgresDatastore
16
- from eventsourcing.tests.postgres_utils import drop_postgres_table
17
-
18
-
19
- class SearchableContentRecorderTestCase(TestCase):
20
- env: ClassVar[Dict[str, str]] = {}
21
-
22
- def test_recorder(self) -> None:
23
- app = SearchableContentApplication(env=self.env)
24
-
25
- # Need to cover the case where select_page() raises PageNotFound.
26
- recorder = cast(SearchableContentRecorder, app.recorder)
27
- with self.assertRaises(PageNotFoundError):
28
- recorder.select_page(uuid4())
29
-
30
-
31
- class TestWithSQLite(SearchableContentRecorderTestCase):
32
- env: ClassVar[Dict[str, str]] = {
33
- "PERSISTENCE_MODULE": "eventsourcing.examples.searchablecontent.sqlite",
34
- "SQLITE_DBNAME": ":memory:",
35
- }
36
-
37
-
38
- class TestWithPostgres(SearchableContentRecorderTestCase):
39
- env: ClassVar[Dict[str, str]] = {
40
- "PERSISTENCE_MODULE": "eventsourcing.examples.searchablecontent.postgres"
41
- }
42
-
43
- def setUp(self) -> None:
44
- super().setUp()
45
- os.environ["POSTGRES_DBNAME"] = "eventsourcing"
46
- os.environ["POSTGRES_HOST"] = "127.0.0.1"
47
- os.environ["POSTGRES_PORT"] = "5432"
48
- os.environ["POSTGRES_USER"] = "eventsourcing"
49
- os.environ["POSTGRES_PASSWORD"] = "eventsourcing" # noqa: S105
50
- self.drop_tables()
51
-
52
- def tearDown(self) -> None:
53
- self.drop_tables()
54
- super().tearDown()
55
-
56
- def drop_tables(self) -> None:
57
- with PostgresDatastore(
58
- os.environ["POSTGRES_DBNAME"],
59
- os.environ["POSTGRES_HOST"],
60
- os.environ["POSTGRES_PORT"],
61
- os.environ["POSTGRES_USER"],
62
- os.environ["POSTGRES_PASSWORD"],
63
- ) as datastore:
64
- drop_postgres_table(datastore, "public.searchablecontentapplication_events")
65
- drop_postgres_table(datastore, "public.pages_projection_example")
66
-
67
-
68
- del SearchableContentRecorderTestCase
File without changes
@@ -1,32 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING, List, cast
4
-
5
- from eventsourcing.examples.cargoshipping.application import BookingApplication
6
- from eventsourcing.examples.cargoshipping.domainmodel import Cargo
7
- from eventsourcing.examples.searchabletimestamps.persistence import (
8
- SearchableTimestampsRecorder,
9
- )
10
-
11
- if TYPE_CHECKING: # pragma: nocover
12
- from datetime import datetime
13
- from uuid import UUID
14
-
15
- from eventsourcing.application import ProcessingEvent
16
- from eventsourcing.persistence import Recording
17
-
18
-
19
- class SearchableTimestampsApplication(BookingApplication):
20
- def _record(self, processing_event: ProcessingEvent) -> List[Recording]:
21
- event_timestamps_data = [
22
- (e.originator_id, e.timestamp, e.originator_version)
23
- for e in processing_event.events
24
- if isinstance(e, Cargo.Event)
25
- ]
26
- processing_event.saved_kwargs["event_timestamps_data"] = event_timestamps_data
27
- return super()._record(processing_event)
28
-
29
- def get_cargo_at_timestamp(self, tracking_id: UUID, timestamp: datetime) -> Cargo:
30
- recorder = cast(SearchableTimestampsRecorder, self.recorder)
31
- version = recorder.get_version_at_timestamp(tracking_id, timestamp)
32
- return cast(Cargo, self.repository.get(tracking_id, version=version))
@@ -1,20 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from abc import abstractmethod
4
- from typing import TYPE_CHECKING
5
-
6
- from eventsourcing.persistence import ApplicationRecorder
7
-
8
- if TYPE_CHECKING: # pragma: nocover
9
- from datetime import datetime
10
- from uuid import UUID
11
-
12
-
13
- class SearchableTimestampsRecorder(ApplicationRecorder):
14
- @abstractmethod
15
- def get_version_at_timestamp(
16
- self, originator_id: UUID, timestamp: datetime
17
- ) -> int | None:
18
- """
19
- Returns originator version at timestamp for given originator ID.
20
- """
@@ -1,110 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from datetime import datetime
4
- from typing import TYPE_CHECKING, Any, List, 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.postgres import (
12
- Factory,
13
- PostgresApplicationRecorder,
14
- PostgresDatastore,
15
- )
16
-
17
- if TYPE_CHECKING: # pragma: nocover
18
- from psycopg import Cursor
19
- from psycopg.rows import DictRow
20
-
21
- from eventsourcing.persistence import ApplicationRecorder, StoredEvent
22
-
23
-
24
- class SearchableTimestampsApplicationRecorder(
25
- SearchableTimestampsRecorder, PostgresApplicationRecorder
26
- ):
27
- def __init__(
28
- self,
29
- datastore: PostgresDatastore,
30
- events_table_name: str = "stored_events",
31
- event_timestamps_table_name: str = "event_timestamps",
32
- ):
33
- self.check_table_name_length(event_timestamps_table_name, datastore.schema)
34
- self.event_timestamps_table_name = event_timestamps_table_name
35
- super().__init__(datastore, events_table_name)
36
- self.insert_event_timestamp_statement = (
37
- f"INSERT INTO {self.event_timestamps_table_name} VALUES (%s, %s, %s)"
38
- )
39
- self.select_event_timestamp_statement = (
40
- f"SELECT originator_version FROM {self.event_timestamps_table_name} WHERE "
41
- "originator_id = %s AND "
42
- "timestamp <= %s "
43
- "ORDER BY originator_version DESC "
44
- "LIMIT 1"
45
- )
46
-
47
- def construct_create_table_statements(self) -> List[str]:
48
- statements = super().construct_create_table_statements()
49
- statements.append(
50
- "CREATE TABLE IF NOT EXISTS "
51
- f"{self.event_timestamps_table_name} ("
52
- "originator_id uuid NOT NULL, "
53
- "timestamp timestamp with time zone, "
54
- "originator_version bigint NOT NULL, "
55
- "PRIMARY KEY "
56
- "(originator_id, timestamp))"
57
- )
58
- return statements
59
-
60
- def _insert_events(
61
- self,
62
- c: Cursor[DictRow],
63
- stored_events: List[StoredEvent],
64
- **kwargs: Any,
65
- ) -> None:
66
- # Insert event timestamps.
67
- event_timestamps_data = cast(
68
- List[Tuple[UUID, datetime, int]], kwargs.get("event_timestamps_data")
69
- )
70
- for event_timestamp_data in event_timestamps_data:
71
- c.execute(
72
- query=self.insert_event_timestamp_statement,
73
- params=event_timestamp_data,
74
- prepare=True,
75
- )
76
- super()._insert_events(c, stored_events, **kwargs)
77
-
78
- def get_version_at_timestamp(
79
- self, originator_id: UUID, timestamp: datetime
80
- ) -> int | None:
81
- with self.datastore.transaction(commit=False) as curs:
82
- curs.execute(
83
- query=self.select_event_timestamp_statement,
84
- params=(originator_id, timestamp),
85
- prepare=True,
86
- )
87
- for row in curs.fetchall():
88
- version = row["originator_version"]
89
- break
90
- else:
91
- version = Aggregate.INITIAL_VERSION - 1
92
- return version
93
-
94
-
95
- class SearchableTimestampsInfrastructureFactory(Factory):
96
- def application_recorder(self) -> ApplicationRecorder:
97
- prefix = (self.datastore.schema + ".") if self.datastore.schema else ""
98
- prefix += self.env.name.lower() or "stored"
99
- events_table_name = prefix + "_events"
100
- event_timestamps_table_name = prefix + "_timestamps"
101
- recorder = SearchableTimestampsApplicationRecorder(
102
- datastore=self.datastore,
103
- events_table_name=events_table_name,
104
- event_timestamps_table_name=event_timestamps_table_name,
105
- )
106
- recorder.create_table()
107
- return recorder
108
-
109
-
110
- del Factory
@@ -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)