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,126 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING, Any, Dict, List, cast
4
-
5
- from eventsourcing.application import Application
6
- from eventsourcing.examples.cargoshipping.domainmodel import (
7
- REGISTERED_ROUTES,
8
- Cargo,
9
- HandlingActivity,
10
- Itinerary,
11
- Leg,
12
- Location,
13
- )
14
- from eventsourcing.persistence import Transcoder, Transcoding
15
-
16
- if TYPE_CHECKING: # pragma: nocover
17
- from datetime import datetime
18
- from uuid import UUID
19
-
20
-
21
- class LocationAsName(Transcoding):
22
- type = Location
23
- name = "location"
24
-
25
- def encode(self, obj: Location) -> str:
26
- return obj.name
27
-
28
- def decode(self, data: str) -> Location:
29
- assert isinstance(data, str)
30
- return Location[data]
31
-
32
-
33
- class HandlingActivityAsName(Transcoding):
34
- type = HandlingActivity
35
- name = "handling_activity"
36
-
37
- def encode(self, obj: HandlingActivity) -> str:
38
- return obj.name
39
-
40
- def decode(self, data: str) -> HandlingActivity:
41
- assert isinstance(data, str)
42
- return HandlingActivity[data]
43
-
44
-
45
- class ItineraryAsDict(Transcoding):
46
- type = Itinerary
47
- name = "itinerary"
48
-
49
- def encode(self, obj: Itinerary) -> Dict[str, Any]:
50
- return obj.__dict__
51
-
52
- def decode(self, data: Dict[str, Any]) -> Itinerary:
53
- assert isinstance(data, dict)
54
- return Itinerary(**data)
55
-
56
-
57
- class LegAsDict(Transcoding):
58
- type = Leg
59
- name = "leg"
60
-
61
- def encode(self, obj: Leg) -> Dict[str, Any]:
62
- return obj.__dict__
63
-
64
- def decode(self, data: Dict[str, Any]) -> Leg:
65
- assert isinstance(data, dict)
66
- return Leg(**data)
67
-
68
-
69
- class BookingApplication(Application):
70
- def register_transcodings(self, transcoder: Transcoder) -> None:
71
- super().register_transcodings(transcoder)
72
- transcoder.register(LocationAsName())
73
- transcoder.register(HandlingActivityAsName())
74
- transcoder.register(ItineraryAsDict())
75
- transcoder.register(LegAsDict())
76
-
77
- def book_new_cargo(
78
- self,
79
- origin: Location,
80
- destination: Location,
81
- arrival_deadline: datetime,
82
- ) -> UUID:
83
- cargo = Cargo.new_booking(origin, destination, arrival_deadline)
84
- self.save(cargo)
85
- return cargo.id
86
-
87
- def change_destination(self, tracking_id: UUID, destination: Location) -> None:
88
- cargo = self.get_cargo(tracking_id)
89
- cargo.change_destination(destination)
90
- self.save(cargo)
91
-
92
- def request_possible_routes_for_cargo(self, tracking_id: UUID) -> List[Itinerary]:
93
- cargo = self.get_cargo(tracking_id)
94
- from_location = (cargo.last_known_location or cargo.origin).value
95
- to_location = cargo.destination.value
96
- try:
97
- possible_routes = REGISTERED_ROUTES[(from_location, to_location)]
98
- except KeyError:
99
- msg = f"Can't find routes from {from_location} to {to_location}"
100
- raise ValueError(msg) from None
101
-
102
- return possible_routes
103
-
104
- def assign_route(self, tracking_id: UUID, itinerary: Itinerary) -> None:
105
- cargo = self.get_cargo(tracking_id)
106
- cargo.assign_route(itinerary)
107
- self.save(cargo)
108
-
109
- def register_handling_event(
110
- self,
111
- tracking_id: UUID,
112
- voyage_number: str | None,
113
- location: Location,
114
- handing_activity: HandlingActivity,
115
- ) -> None:
116
- cargo = self.get_cargo(tracking_id)
117
- cargo.register_handling_event(
118
- tracking_id,
119
- voyage_number,
120
- location,
121
- handing_activity,
122
- )
123
- self.save(cargo)
124
-
125
- def get_cargo(self, tracking_id: UUID) -> Cargo:
126
- return cast(Cargo, self.repository.get(tracking_id))
@@ -1,330 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from datetime import datetime, timedelta
4
- from enum import Enum
5
- from typing import Dict, List, Optional, Tuple, Union, cast
6
- from uuid import UUID, uuid4
7
-
8
- from eventsourcing.dispatch import singledispatchmethod
9
- from eventsourcing.domain import Aggregate
10
-
11
-
12
- class Location(Enum):
13
- """
14
- Locations in the world.
15
- """
16
-
17
- HAMBURG = "HAMBURG"
18
- HONGKONG = "HONGKONG"
19
- NEWYORK = "NEWYORK"
20
- STOCKHOLM = "STOCKHOLM"
21
- TOKYO = "TOKYO"
22
-
23
- NLRTM = "NLRTM"
24
- USDAL = "USDAL"
25
- AUMEL = "AUMEL"
26
-
27
-
28
- class Leg:
29
- """
30
- Leg of an itinerary.
31
- """
32
-
33
- def __init__(
34
- self,
35
- origin: str,
36
- destination: str,
37
- voyage_number: str,
38
- ):
39
- self.origin: str = origin
40
- self.destination: str = destination
41
- self.voyage_number: str = voyage_number
42
-
43
-
44
- class Itinerary:
45
- """
46
- An itinerary along which cargo is shipped.
47
- """
48
-
49
- def __init__(
50
- self,
51
- origin: str,
52
- destination: str,
53
- legs: Tuple[Leg, ...],
54
- ):
55
- self.origin = origin
56
- self.destination = destination
57
- self.legs = legs
58
-
59
-
60
- class HandlingActivity(Enum):
61
- RECEIVE = "RECEIVE"
62
- LOAD = "LOAD"
63
- UNLOAD = "UNLOAD"
64
- CLAIM = "CLAIM"
65
-
66
-
67
- # Custom static types.
68
- LegDetails = Dict[str, str]
69
-
70
- ItineraryDetails = Dict[str, Union[str, List[LegDetails]]]
71
-
72
- NextExpectedActivity = Optional[Tuple[HandlingActivity, Location, str]]
73
-
74
-
75
- # Some routes from one location to another.
76
- REGISTERED_ROUTES = {
77
- ("HONGKONG", "STOCKHOLM"): [
78
- Itinerary(
79
- origin="HONGKONG",
80
- destination="STOCKHOLM",
81
- legs=(
82
- Leg(
83
- origin="HONGKONG",
84
- destination="NEWYORK",
85
- voyage_number="V1",
86
- ),
87
- Leg(
88
- origin="NEWYORK",
89
- destination="STOCKHOLM",
90
- voyage_number="V2",
91
- ),
92
- ),
93
- )
94
- ],
95
- ("TOKYO", "STOCKHOLM"): [
96
- Itinerary(
97
- origin="TOKYO",
98
- destination="STOCKHOLM",
99
- legs=(
100
- Leg(
101
- origin="TOKYO",
102
- destination="HAMBURG",
103
- voyage_number="V3",
104
- ),
105
- Leg(
106
- origin="HAMBURG",
107
- destination="STOCKHOLM",
108
- voyage_number="V4",
109
- ),
110
- ),
111
- )
112
- ],
113
- }
114
-
115
-
116
- class Cargo(Aggregate):
117
- """
118
- The Cargo aggregate is an event-sourced domain model aggregate that
119
- specifies the routing from origin to destination, and can track what
120
- happens to the cargo after it has been booked.
121
- """
122
-
123
- def __init__(
124
- self,
125
- origin: Location,
126
- destination: Location,
127
- arrival_deadline: datetime,
128
- ):
129
- self._origin: Location = origin
130
- self._destination: Location = destination
131
- self._arrival_deadline: datetime = arrival_deadline
132
- self._transport_status: str = "NOT_RECEIVED"
133
- self._routing_status: str = "NOT_ROUTED"
134
- self._is_misdirected: bool = False
135
- self._estimated_time_of_arrival: datetime | None = None
136
- self._next_expected_activity: NextExpectedActivity = None
137
- self._route: Itinerary | None = None
138
- self._last_known_location: Location | None = None
139
- self._current_voyage_number: str | None = None
140
-
141
- @property
142
- def origin(self) -> Location:
143
- return self._origin
144
-
145
- @property
146
- def destination(self) -> Location:
147
- return self._destination
148
-
149
- @property
150
- def arrival_deadline(self) -> datetime:
151
- return self._arrival_deadline
152
-
153
- @property
154
- def transport_status(self) -> str:
155
- return self._transport_status
156
-
157
- @property
158
- def routing_status(self) -> str:
159
- return self._routing_status
160
-
161
- @property
162
- def is_misdirected(self) -> bool:
163
- return self._is_misdirected
164
-
165
- @property
166
- def estimated_time_of_arrival(
167
- self,
168
- ) -> datetime | None:
169
- return self._estimated_time_of_arrival
170
-
171
- @property
172
- def next_expected_activity(self) -> NextExpectedActivity:
173
- return self._next_expected_activity
174
-
175
- @property
176
- def route(self) -> Itinerary | None:
177
- return self._route
178
-
179
- @property
180
- def last_known_location(self) -> Location | None:
181
- return self._last_known_location
182
-
183
- @property
184
- def current_voyage_number(self) -> str | None:
185
- return self._current_voyage_number
186
-
187
- @classmethod
188
- def new_booking(
189
- cls,
190
- origin: Location,
191
- destination: Location,
192
- arrival_deadline: datetime,
193
- ) -> Cargo:
194
- return cls._create(
195
- event_class=cls.BookingStarted,
196
- id=uuid4(),
197
- origin=origin,
198
- destination=destination,
199
- arrival_deadline=arrival_deadline,
200
- )
201
-
202
- class BookingStarted(Aggregate.Created):
203
- origin: Location
204
- destination: Location
205
- arrival_deadline: datetime
206
-
207
- class Event(Aggregate.Event):
208
- def apply(self, aggregate: Aggregate) -> None:
209
- cast(Cargo, aggregate).when(self)
210
-
211
- @singledispatchmethod
212
- def when(self, event: Event) -> None:
213
- """
214
- Default method to apply an aggregate event to the aggregate object.
215
- """
216
-
217
- def change_destination(self, destination: Location) -> None:
218
- self.trigger_event(
219
- self.DestinationChanged,
220
- destination=destination,
221
- )
222
-
223
- class DestinationChanged(Event):
224
- destination: Location
225
-
226
- @when.register
227
- def _(self, event: Cargo.DestinationChanged) -> None:
228
- self._destination = event.destination
229
-
230
- def assign_route(self, itinerary: Itinerary) -> None:
231
- self.trigger_event(self.RouteAssigned, route=itinerary)
232
-
233
- class RouteAssigned(Event):
234
- route: Itinerary
235
-
236
- @when.register
237
- def _(self, event: Cargo.RouteAssigned) -> None:
238
- self._route = event.route
239
- self._routing_status = "ROUTED"
240
- self._estimated_time_of_arrival = Cargo.Event.create_timestamp() + timedelta(
241
- weeks=1
242
- )
243
- self._next_expected_activity = (HandlingActivity.RECEIVE, self.origin, "")
244
- self._is_misdirected = False
245
-
246
- def register_handling_event(
247
- self,
248
- tracking_id: UUID,
249
- voyage_number: str | None,
250
- location: Location,
251
- handling_activity: HandlingActivity,
252
- ) -> None:
253
- self.trigger_event(
254
- self.HandlingEventRegistered,
255
- tracking_id=tracking_id,
256
- voyage_number=voyage_number,
257
- location=location,
258
- handling_activity=handling_activity,
259
- )
260
-
261
- class HandlingEventRegistered(Event):
262
- tracking_id: UUID
263
- voyage_number: str
264
- location: Location
265
- handling_activity: str
266
-
267
- @when.register
268
- def _(self, event: Cargo.HandlingEventRegistered) -> None:
269
- assert self.route is not None
270
- if event.handling_activity == HandlingActivity.RECEIVE:
271
- self._transport_status = "IN_PORT"
272
- self._last_known_location = event.location
273
- self._next_expected_activity = (
274
- HandlingActivity.LOAD,
275
- event.location,
276
- self.route.legs[0].voyage_number,
277
- )
278
- elif event.handling_activity == HandlingActivity.LOAD:
279
- self._transport_status = "ONBOARD_CARRIER"
280
- self._current_voyage_number = event.voyage_number
281
- for leg in self.route.legs:
282
- if (
283
- leg.origin == event.location.value
284
- and leg.voyage_number == event.voyage_number
285
- ):
286
- self._next_expected_activity = (
287
- HandlingActivity.UNLOAD,
288
- Location[leg.destination],
289
- event.voyage_number,
290
- )
291
- break
292
- else:
293
- msg = "Can't find leg with origin={} and voyage_number={}".format(
294
- event.location,
295
- event.voyage_number,
296
- )
297
- raise Exception(msg)
298
-
299
- elif event.handling_activity == HandlingActivity.UNLOAD:
300
- self._current_voyage_number = None
301
- self._last_known_location = event.location
302
- self._transport_status = "IN_PORT"
303
- if event.location == self.destination:
304
- self._next_expected_activity = (
305
- HandlingActivity.CLAIM,
306
- event.location,
307
- "",
308
- )
309
- elif event.location.value in [leg.destination for leg in self.route.legs]:
310
- for i, leg in enumerate(self.route.legs):
311
- if leg.voyage_number == event.voyage_number:
312
- next_leg: Leg = self.route.legs[i + 1]
313
- assert Location[next_leg.origin] == event.location
314
- self._next_expected_activity = (
315
- HandlingActivity.LOAD,
316
- event.location,
317
- next_leg.voyage_number,
318
- )
319
- break
320
- else:
321
- self._is_misdirected = True
322
- self._next_expected_activity = None
323
-
324
- elif event.handling_activity == HandlingActivity.CLAIM:
325
- self._next_expected_activity = None
326
- self._transport_status = "CLAIMED"
327
-
328
- else:
329
- msg = f"Unsupported handling event: {event.handling_activity}"
330
- raise Exception(msg)
@@ -1,143 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from datetime import datetime
4
- from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
5
- from uuid import UUID
6
-
7
- from eventsourcing.examples.cargoshipping.domainmodel import (
8
- HandlingActivity,
9
- Itinerary,
10
- ItineraryDetails,
11
- LegDetails,
12
- Location,
13
- )
14
-
15
- if TYPE_CHECKING: # pragma: nocover
16
- from eventsourcing.examples.cargoshipping.application import BookingApplication
17
-
18
- NextExpectedActivityDetails = Optional[Tuple[str, ...]]
19
- CargoDetails = Dict[
20
- str, Optional[Union[str, bool, datetime, NextExpectedActivityDetails]]
21
- ]
22
-
23
-
24
- class BookingService:
25
- """
26
- Presents an application interface that uses
27
- simple types of object (str, bool, datetime).
28
- """
29
-
30
- def __init__(self, app: BookingApplication):
31
- self.app = app
32
-
33
- def book_new_cargo(
34
- self,
35
- origin: str,
36
- destination: str,
37
- arrival_deadline: datetime,
38
- ) -> str:
39
- tracking_id = self.app.book_new_cargo(
40
- Location[origin],
41
- Location[destination],
42
- arrival_deadline,
43
- )
44
- return str(tracking_id)
45
-
46
- def get_cargo_details(self, tracking_id: str) -> CargoDetails:
47
- cargo = self.app.get_cargo(UUID(tracking_id))
48
-
49
- # Present 'next_expected_activity'.
50
- next_expected_activity: NextExpectedActivityDetails
51
- if cargo.next_expected_activity is None:
52
- next_expected_activity = None
53
- elif len(cargo.next_expected_activity) == 2:
54
- next_expected_activity = (
55
- cargo.next_expected_activity[0].value,
56
- cargo.next_expected_activity[1].value,
57
- )
58
- elif len(cargo.next_expected_activity) == 3:
59
- next_expected_activity = (
60
- cargo.next_expected_activity[0].value,
61
- cargo.next_expected_activity[1].value,
62
- cargo.next_expected_activity[2],
63
- )
64
- else:
65
- msg = f"Invalid next expected activity: {cargo.next_expected_activity}"
66
- raise Exception(msg)
67
-
68
- # Present 'last_known_location'.
69
- if cargo.last_known_location is None:
70
- last_known_location = None
71
- else:
72
- last_known_location = cargo.last_known_location.value
73
-
74
- # Present the cargo details.
75
- return {
76
- "id": str(cargo.id),
77
- "origin": cargo.origin.value,
78
- "destination": cargo.destination.value,
79
- "arrival_deadline": cargo.arrival_deadline,
80
- "transport_status": cargo.transport_status,
81
- "routing_status": cargo.routing_status,
82
- "is_misdirected": cargo.is_misdirected,
83
- "estimated_time_of_arrival": cargo.estimated_time_of_arrival,
84
- "next_expected_activity": next_expected_activity,
85
- "last_known_location": last_known_location,
86
- "current_voyage_number": cargo.current_voyage_number,
87
- }
88
-
89
- def change_destination(self, tracking_id: str, destination: str) -> None:
90
- self.app.change_destination(UUID(tracking_id), Location[destination])
91
-
92
- def request_possible_routes_for_cargo(
93
- self, tracking_id: str
94
- ) -> List[ItineraryDetails]:
95
- routes = self.app.request_possible_routes_for_cargo(UUID(tracking_id))
96
- return [self.dict_from_itinerary(route) for route in routes]
97
-
98
- def dict_from_itinerary(self, itinerary: Itinerary) -> ItineraryDetails:
99
- legs_details = []
100
- for leg in itinerary.legs:
101
- leg_details: LegDetails = {
102
- "origin": leg.origin,
103
- "destination": leg.destination,
104
- "voyage_number": leg.voyage_number,
105
- }
106
- legs_details.append(leg_details)
107
- route_details: ItineraryDetails = {
108
- "origin": itinerary.origin,
109
- "destination": itinerary.destination,
110
- "legs": legs_details,
111
- }
112
- return route_details
113
-
114
- def assign_route(
115
- self,
116
- tracking_id: str,
117
- route_details: ItineraryDetails,
118
- ) -> None:
119
- routes = self.app.request_possible_routes_for_cargo(UUID(tracking_id))
120
- for route in routes:
121
- if route_details == self.dict_from_itinerary(route):
122
- self.app.assign_route(UUID(tracking_id), route)
123
-
124
- def register_handling_event(
125
- self,
126
- tracking_id: str,
127
- voyage_number: str | None,
128
- location: str,
129
- handling_activity: str,
130
- ) -> None:
131
- self.app.register_handling_event(
132
- UUID(tracking_id),
133
- voyage_number,
134
- Location[location],
135
- HandlingActivity[handling_activity],
136
- )
137
-
138
-
139
- # Stub function that picks an itinerary from a list of possible itineraries.
140
- def select_preferred_itinerary(
141
- itineraries: List[ItineraryDetails],
142
- ) -> ItineraryDetails:
143
- return itineraries[0]