Skip to content

Commit 0a12c0a

Browse files
committed
fix(application): use ports for serializable transactions (#62)
1 parent f0ab80a commit 0a12c0a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+550
-223
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class SerializationError(Exception): ...

src/ttt/application/common/ports/map.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ async def __call__(
2323
"""
2424
:raises ttt.application.common.ports.map.NotUniqueUserIdError:
2525
:raises ttt.application.common.ports.map.NotUniqueActiveInvitationToGameUserIdsError:
26+
:raises ttt.application.common.errors.serialization_error.SerializationError:
2627
""" # noqa: E501
Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,33 @@
1+
from abc import ABC, abstractmethod
12
from contextlib import AbstractAsyncContextManager
3+
from types import TracebackType
24
from typing import Any
35

46

5-
Transaction = AbstractAsyncContextManager[Any]
7+
class SerializableTransaction(AbstractAsyncContextManager[Any], ABC):
8+
@abstractmethod
9+
async def __aexit__(
10+
self,
11+
exc_type: type[BaseException] | None,
12+
exc_value: BaseException | None,
13+
traceback: TracebackType | None,
14+
) -> bool | None:
15+
"""
16+
:raises ttt.application.common.errors.serialization_error.SerializationError:
17+
""" # noqa: E501
18+
19+
return None
20+
21+
@abstractmethod
22+
async def commit(self) -> None:
23+
"""
24+
:raises ttt.application.common.errors.serialization_error.SerializationError:
25+
""" # noqa: E501
26+
27+
28+
class NotSerializableTransaction(AbstractAsyncContextManager[Any], ABC):
29+
@abstractmethod
30+
async def commit(self) -> None: ...
31+
32+
33+
class ReadonlyTransaction(AbstractAsyncContextManager[Any], ABC): ...

src/ttt/application/game/game/cancel_game.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from dataclasses import dataclass
22

33
from ttt.application.common.ports.map import Map
4-
from ttt.application.common.ports.transaction import Transaction
4+
from ttt.application.common.ports.transaction import SerializableTransaction
55
from ttt.application.common.ports.uuids import UUIDs
66
from ttt.application.game.game.ports.game_log import GameLog
77
from ttt.application.game.game.ports.game_views import GameViews
@@ -16,29 +16,38 @@ class CancelGame:
1616
games: Games
1717
game_views: GameViews
1818
uuids: UUIDs
19-
transaction: Transaction
19+
transaction: SerializableTransaction
2020
log: GameLog
2121

2222
async def __call__(self, user_id: int) -> None:
23+
"""
24+
:raises ttt.application.common.errors.serialization_error.SerializationError:
25+
""" # noqa: E501
26+
2327
async with self.transaction:
2428
game = await self.games.current_user_game(user_id)
2529

2630
if game is None:
27-
await self.game_views.no_game_view(user_id)
31+
await self.log.no_current_game_to_cancel_game(user_id)
32+
await self.transaction.commit()
33+
await self.game_views.no_current_game_view(user_id)
2834
return
2935

3036
try:
3137
tracking = Tracking()
3238
game.cancel(user_id, tracking)
3339
except AlreadyCompletedGameError:
3440
await self.log.already_completed_game_to_cancel(game, user_id)
41+
await self.transaction.commit()
3542
await self.game_views.game_already_complteted_view(
3643
user_id,
3744
game,
3845
)
3946
return
47+
else:
48+
await self.log.game_cancelled(user_id, game)
4049

41-
await self.log.game_cancelled(user_id, game)
50+
await self.map_(tracking)
51+
await self.transaction.commit()
4252

43-
await self.map_(tracking)
44-
await self.game_views.game_view(game)
53+
await self.game_views.game_view(game)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from asyncio import gather
2+
from dataclasses import dataclass
3+
from uuid import UUID
4+
5+
from ttt.application.common.ports.map import Map
6+
from ttt.application.common.ports.randoms import Randoms
7+
from ttt.application.common.ports.transaction import SerializableTransaction
8+
from ttt.application.common.ports.uuids import UUIDs
9+
from ttt.application.game.game.ports.game_ai_gateway import GameAiGateway
10+
from ttt.application.game.game.ports.game_dao import GameDao
11+
from ttt.application.game.game.ports.game_log import GameLog
12+
from ttt.application.game.game.ports.game_views import GameViews
13+
from ttt.application.game.game.ports.games import Games
14+
from ttt.application.user.common.ports.users import Users
15+
from ttt.entities.core.game.game import (
16+
AlreadyCompletedGameError,
17+
NotAiCurrentMoveError,
18+
)
19+
from ttt.entities.tools.tracking import Tracking
20+
21+
22+
@dataclass(frozen=True, unsafe_hash=False)
23+
class MakeAiMoveInGame:
24+
map_: Map
25+
games: Games
26+
game_views: GameViews
27+
users: Users
28+
uuids: UUIDs
29+
randoms: Randoms
30+
ai_gateway: GameAiGateway
31+
transaction: SerializableTransaction
32+
log: GameLog
33+
dao: GameDao
34+
35+
async def __call__(self, game_id: UUID, ai_id: UUID) -> None:
36+
"""
37+
:raises ttt.application.common.errors.serialization_error.SerializationError:
38+
""" # noqa: E501
39+
40+
async with self.transaction:
41+
game = await self.games.game_with_id(game_id)
42+
43+
if game is None:
44+
await self.log.no_game_to_make_ai_move(game_id)
45+
return
46+
47+
(
48+
free_cell_random,
49+
ai_move_cell_number_int,
50+
) = await gather(
51+
self.randoms.random(),
52+
self.ai_gateway.next_move_cell_number_int(game, ai_id),
53+
)
54+
try:
55+
tracking = Tracking()
56+
ai_move = game.make_ai_move(
57+
ai_id,
58+
ai_move_cell_number_int,
59+
free_cell_random,
60+
tracking,
61+
)
62+
except AlreadyCompletedGameError:
63+
await self.log.already_completed_game_to_make_ai_move(
64+
game, ai_id,
65+
)
66+
except NotAiCurrentMoveError:
67+
await self.log.not_ai_current_move_to_make_ai_move(
68+
game, ai_id,
69+
)
70+
else:
71+
await self.log.ai_move_maked(game, ai_move, ai_id)
72+
73+
if game.is_completed():
74+
await self.log.game_was_completed_by_ai(ai_id, game)
75+
76+
await self.map_(tracking)
77+
await self.transaction.commit()
78+
79+
await self.game_views.game_view(game)

src/ttt/application/game/game/make_move_in_game.py

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33

44
from ttt.application.common.ports.map import Map
55
from ttt.application.common.ports.randoms import Randoms
6-
from ttt.application.common.ports.transaction import Transaction
6+
from ttt.application.common.ports.transaction import SerializableTransaction
77
from ttt.application.common.ports.uuids import UUIDs
88
from ttt.application.game.game.ports.game_ai_gateway import GameAiGateway
99
from ttt.application.game.game.ports.game_dao import GameDao
1010
from ttt.application.game.game.ports.game_log import GameLog
11+
from ttt.application.game.game.ports.game_tasks import GameTasks
1112
from ttt.application.game.game.ports.game_views import GameViews
1213
from ttt.application.game.game.ports.games import Games
1314
from ttt.application.user.common.ports.users import Users
@@ -29,20 +30,25 @@ class MakeMoveInGame:
2930
uuids: UUIDs
3031
randoms: Randoms
3132
ai_gateway: GameAiGateway
32-
transaction: Transaction
33+
transaction: SerializableTransaction
3334
log: GameLog
3435
dao: GameDao
36+
tasks: GameTasks
3537

3638
async def __call__(
3739
self,
3840
user_id: int,
3941
cell_number_int: int,
4042
) -> None:
43+
"""
44+
:raises ttt.application.common.errors.serialization_error.SerializationError:
45+
""" # noqa: E501
46+
4147
async with self.transaction:
4248
game = await self.games.current_user_game(user_id)
4349

4450
if game is None:
45-
await self.game_views.no_game_view(user_id)
51+
await self.game_views.no_current_game_view(user_id)
4652
return
4753

4854
(
@@ -68,6 +74,7 @@ async def __call__(
6874
user_id,
6975
cell_number_int,
7076
)
77+
await self.transaction.commit()
7178
await self.game_views.game_already_complteted_view(
7279
user_id,
7380
game,
@@ -78,6 +85,7 @@ async def __call__(
7885
user_id,
7986
cell_number_int,
8087
)
88+
await self.transaction.commit()
8189
await self.game_views.not_current_user_view(
8290
user_id,
8391
game,
@@ -88,48 +96,31 @@ async def __call__(
8896
user_id,
8997
cell_number_int,
9098
)
99+
await self.transaction.commit()
91100
await self.game_views.no_cell_view(user_id, game)
92101
except AlreadyFilledCellError:
93102
await self.log.already_filled_cell_to_make_move(
94103
game,
95104
user_id,
96105
cell_number_int,
97106
)
107+
await self.transaction.commit()
98108
await self.game_views.already_filled_cell_error(
99109
user_id,
100110
game,
101111
)
102112
else:
103113
await self.log.user_move_maked(user_id, game, user_move)
104114

105-
if user_move.next_move_ai_id is not None:
106-
await self.game_views.game_view(game)
115+
if game.is_completed():
116+
await self.log.game_was_completed_by_user(user_id, game)
107117

108-
(
109-
free_cell_random,
110-
ai_move_cell_number_int,
111-
) = await gather(
112-
self.randoms.random(),
113-
self.ai_gateway.next_move_cell_number_int(
114-
game,
115-
user_move.next_move_ai_id,
116-
),
117-
)
118-
ai_move = game.make_ai_move(
119-
user_move.next_move_ai_id,
120-
ai_move_cell_number_int,
121-
free_cell_random,
122-
tracking,
123-
)
118+
await self.map_(tracking)
124119

125-
await self.log.ai_move_maked(
126-
user_id,
127-
game,
128-
ai_move,
120+
if user_move.next_move_ai_id is not None:
121+
await self.tasks.make_ai_move(
122+
game.id, user_move.next_move_ai_id,
129123
)
130124

131-
if game.is_completed():
132-
await self.log.game_completed(user_id, game)
133-
134-
await self.map_(tracking)
125+
await self.transaction.commit()
135126
await self.game_views.game_view(game)

src/ttt/application/game/game/ports/game_log.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,40 @@
11
from abc import ABC, abstractmethod
2+
from uuid import UUID
23

34
from ttt.entities.core.game.game import Game
45
from ttt.entities.core.game.move import AiMove, UserMove
56
from ttt.entities.core.user.user import User
67

78

89
class GameLog(ABC):
10+
@abstractmethod
11+
async def no_current_game_to_make_move(
12+
self,
13+
user_id: int,
14+
/,
15+
) -> None: ...
16+
17+
@abstractmethod
18+
async def no_current_game_to_cancel_game(
19+
self,
20+
user_id: int,
21+
/,
22+
) -> None: ...
23+
24+
@abstractmethod
25+
async def no_game_to_make_ai_move(
26+
self,
27+
game_id: UUID,
28+
/,
29+
) -> None: ...
30+
31+
@abstractmethod
32+
async def no_current_game(
33+
self,
34+
user_id: int,
35+
/,
36+
) -> None: ...
37+
938
@abstractmethod
1039
async def game_against_ai_started(
1140
self,
@@ -33,20 +62,28 @@ async def user_move_maked(
3362
@abstractmethod
3463
async def ai_move_maked(
3564
self,
36-
user_id: int,
3765
game: Game,
3866
move: AiMove,
67+
ai_id: UUID,
3968
/,
4069
) -> None: ...
4170

4271
@abstractmethod
43-
async def game_completed(
72+
async def game_was_completed_by_user(
4473
self,
4574
user_id: int,
4675
game: Game,
4776
/,
4877
) -> None: ...
4978

79+
@abstractmethod
80+
async def game_was_completed_by_ai(
81+
self,
82+
ai_id: UUID,
83+
game: Game,
84+
/,
85+
) -> None: ...
86+
5087
@abstractmethod
5188
async def user_already_in_game_to_start_game_against_ai(
5289
self, user: User, /,
@@ -96,3 +133,19 @@ async def already_completed_game_to_cancel(
96133
user_id: int,
97134
/,
98135
) -> None: ...
136+
137+
@abstractmethod
138+
async def already_completed_game_to_make_ai_move(
139+
self,
140+
game: Game,
141+
ai_id: UUID,
142+
/,
143+
) -> None: ...
144+
145+
@abstractmethod
146+
async def not_ai_current_move_to_make_ai_move(
147+
self,
148+
game: Game,
149+
ai_id: UUID,
150+
/,
151+
) -> None: ...
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from abc import ABC, abstractmethod
2+
from uuid import UUID
3+
4+
5+
class GameTasks(ABC):
6+
@abstractmethod
7+
async def make_ai_move(
8+
self,
9+
game_id: UUID,
10+
ai_id: UUID,
11+
/,
12+
) -> None: ...

0 commit comments

Comments
 (0)