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.
- eventsourcing/application.py +0 -15
- eventsourcing/system.py +25 -2
- {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.5.dist-info}/METADATA +1 -1
- eventsourcing-9.3.5.dist-info/RECORD +24 -0
- eventsourcing/examples/__init__.py +0 -0
- eventsourcing/examples/aggregate1/__init__.py +0 -0
- eventsourcing/examples/aggregate1/application.py +0 -27
- eventsourcing/examples/aggregate1/domainmodel.py +0 -16
- eventsourcing/examples/aggregate1/test_application.py +0 -37
- eventsourcing/examples/aggregate2/__init__.py +0 -0
- eventsourcing/examples/aggregate2/application.py +0 -27
- eventsourcing/examples/aggregate2/domainmodel.py +0 -22
- eventsourcing/examples/aggregate2/test_application.py +0 -37
- eventsourcing/examples/aggregate3/__init__.py +0 -0
- eventsourcing/examples/aggregate3/application.py +0 -27
- eventsourcing/examples/aggregate3/domainmodel.py +0 -38
- eventsourcing/examples/aggregate3/test_application.py +0 -37
- eventsourcing/examples/aggregate4/__init__.py +0 -0
- eventsourcing/examples/aggregate4/application.py +0 -27
- eventsourcing/examples/aggregate4/domainmodel.py +0 -114
- eventsourcing/examples/aggregate4/test_application.py +0 -38
- eventsourcing/examples/aggregate5/__init__.py +0 -0
- eventsourcing/examples/aggregate5/application.py +0 -27
- eventsourcing/examples/aggregate5/domainmodel.py +0 -131
- eventsourcing/examples/aggregate5/test_application.py +0 -38
- eventsourcing/examples/aggregate6/__init__.py +0 -0
- eventsourcing/examples/aggregate6/application.py +0 -30
- eventsourcing/examples/aggregate6/domainmodel.py +0 -123
- eventsourcing/examples/aggregate6/test_application.py +0 -38
- eventsourcing/examples/aggregate6a/__init__.py +0 -0
- eventsourcing/examples/aggregate6a/application.py +0 -40
- eventsourcing/examples/aggregate6a/domainmodel.py +0 -149
- eventsourcing/examples/aggregate6a/test_application.py +0 -45
- eventsourcing/examples/aggregate7/__init__.py +0 -0
- eventsourcing/examples/aggregate7/application.py +0 -53
- eventsourcing/examples/aggregate7/domainmodel.py +0 -142
- eventsourcing/examples/aggregate7/persistence.py +0 -57
- eventsourcing/examples/aggregate7/test_application.py +0 -45
- eventsourcing/examples/aggregate7/test_compression_and_encryption.py +0 -45
- eventsourcing/examples/aggregate7/test_snapshotting_intervals.py +0 -67
- eventsourcing/examples/aggregate7a/__init__.py +0 -0
- eventsourcing/examples/aggregate7a/application.py +0 -56
- eventsourcing/examples/aggregate7a/domainmodel.py +0 -168
- eventsourcing/examples/aggregate7a/test_application.py +0 -46
- eventsourcing/examples/aggregate7a/test_compression_and_encryption.py +0 -45
- eventsourcing/examples/aggregate8/__init__.py +0 -0
- eventsourcing/examples/aggregate8/application.py +0 -47
- eventsourcing/examples/aggregate8/domainmodel.py +0 -71
- eventsourcing/examples/aggregate8/persistence.py +0 -57
- eventsourcing/examples/aggregate8/test_application.py +0 -44
- eventsourcing/examples/aggregate8/test_compression_and_encryption.py +0 -44
- eventsourcing/examples/aggregate8/test_snapshotting_intervals.py +0 -38
- eventsourcing/examples/bankaccounts/__init__.py +0 -0
- eventsourcing/examples/bankaccounts/application.py +0 -70
- eventsourcing/examples/bankaccounts/domainmodel.py +0 -56
- eventsourcing/examples/bankaccounts/test.py +0 -173
- eventsourcing/examples/cargoshipping/__init__.py +0 -0
- eventsourcing/examples/cargoshipping/application.py +0 -126
- eventsourcing/examples/cargoshipping/domainmodel.py +0 -330
- eventsourcing/examples/cargoshipping/interface.py +0 -143
- eventsourcing/examples/cargoshipping/test.py +0 -231
- eventsourcing/examples/contentmanagement/__init__.py +0 -0
- eventsourcing/examples/contentmanagement/application.py +0 -118
- eventsourcing/examples/contentmanagement/domainmodel.py +0 -69
- eventsourcing/examples/contentmanagement/test.py +0 -180
- eventsourcing/examples/contentmanagement/utils.py +0 -26
- eventsourcing/examples/contentmanagementsystem/__init__.py +0 -0
- eventsourcing/examples/contentmanagementsystem/application.py +0 -54
- eventsourcing/examples/contentmanagementsystem/postgres.py +0 -17
- eventsourcing/examples/contentmanagementsystem/sqlite.py +0 -17
- eventsourcing/examples/contentmanagementsystem/system.py +0 -14
- eventsourcing/examples/contentmanagementsystem/test_system.py +0 -180
- eventsourcing/examples/searchablecontent/__init__.py +0 -0
- eventsourcing/examples/searchablecontent/application.py +0 -45
- eventsourcing/examples/searchablecontent/persistence.py +0 -23
- eventsourcing/examples/searchablecontent/postgres.py +0 -118
- eventsourcing/examples/searchablecontent/sqlite.py +0 -136
- eventsourcing/examples/searchablecontent/test_application.py +0 -110
- eventsourcing/examples/searchablecontent/test_recorder.py +0 -68
- eventsourcing/examples/searchabletimestamps/__init__.py +0 -0
- eventsourcing/examples/searchabletimestamps/application.py +0 -32
- eventsourcing/examples/searchabletimestamps/persistence.py +0 -20
- eventsourcing/examples/searchabletimestamps/postgres.py +0 -110
- eventsourcing/examples/searchabletimestamps/sqlite.py +0 -99
- eventsourcing/examples/searchabletimestamps/test_searchabletimestamps.py +0 -94
- eventsourcing/examples/test_invoice.py +0 -176
- eventsourcing/examples/test_parking_lot.py +0 -206
- eventsourcing/tests/application_tests/__init__.py +0 -0
- eventsourcing/tests/application_tests/test_application_with_automatic_snapshotting.py +0 -55
- eventsourcing/tests/application_tests/test_application_with_popo.py +0 -22
- eventsourcing/tests/application_tests/test_application_with_postgres.py +0 -75
- eventsourcing/tests/application_tests/test_application_with_sqlite.py +0 -72
- eventsourcing/tests/application_tests/test_cache.py +0 -134
- eventsourcing/tests/application_tests/test_event_sourced_log.py +0 -162
- eventsourcing/tests/application_tests/test_notificationlog.py +0 -232
- eventsourcing/tests/application_tests/test_notificationlogreader.py +0 -126
- eventsourcing/tests/application_tests/test_processapplication.py +0 -110
- eventsourcing/tests/application_tests/test_processingpolicy.py +0 -109
- eventsourcing/tests/application_tests/test_repository.py +0 -504
- eventsourcing/tests/application_tests/test_snapshotting.py +0 -68
- eventsourcing/tests/application_tests/test_upcasting.py +0 -459
- eventsourcing/tests/docs_tests/__init__.py +0 -0
- eventsourcing/tests/docs_tests/test_docs.py +0 -293
- eventsourcing/tests/domain_tests/__init__.py +0 -0
- eventsourcing/tests/domain_tests/test_aggregate.py +0 -1200
- eventsourcing/tests/domain_tests/test_aggregate_decorators.py +0 -1604
- eventsourcing/tests/domain_tests/test_domainevent.py +0 -80
- eventsourcing/tests/interface_tests/__init__.py +0 -0
- eventsourcing/tests/interface_tests/test_remotenotificationlog.py +0 -258
- eventsourcing/tests/persistence_tests/__init__.py +0 -0
- eventsourcing/tests/persistence_tests/test_aes.py +0 -93
- eventsourcing/tests/persistence_tests/test_connection_pool.py +0 -722
- eventsourcing/tests/persistence_tests/test_eventstore.py +0 -72
- eventsourcing/tests/persistence_tests/test_infrastructure_factory.py +0 -21
- eventsourcing/tests/persistence_tests/test_mapper.py +0 -113
- eventsourcing/tests/persistence_tests/test_noninterleaving_notification_ids.py +0 -69
- eventsourcing/tests/persistence_tests/test_popo.py +0 -124
- eventsourcing/tests/persistence_tests/test_postgres.py +0 -1120
- eventsourcing/tests/persistence_tests/test_sqlite.py +0 -348
- eventsourcing/tests/persistence_tests/test_transcoder.py +0 -44
- eventsourcing/tests/system_tests/__init__.py +0 -0
- eventsourcing/tests/system_tests/test_runner.py +0 -935
- eventsourcing/tests/system_tests/test_system.py +0 -284
- eventsourcing/tests/utils_tests/__init__.py +0 -0
- eventsourcing/tests/utils_tests/test_utils.py +0 -226
- eventsourcing-9.3.3.dist-info/RECORD +0 -145
- {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.5.dist-info}/AUTHORS +0 -0
- {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.5.dist-info}/LICENSE +0 -0
- {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]
|