Skip to content

Commit 8664e90

Browse files
committed
fix: add SerializationError retrying (#62)
1 parent 137da13 commit 8664e90

30 files changed

+185
-58
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from abc import ABC, abstractmethod
2+
3+
4+
class Retry(ABC):
5+
@abstractmethod
6+
def __bool__(self) -> bool: ...

src/ttt/application/stars_purchase/start_stars_purchase_payment.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from ttt.application.common.ports.clock import Clock
66
from ttt.application.common.ports.map import Map
7+
from ttt.application.common.ports.retry import Retry
78
from ttt.application.common.ports.transaction import SerializableTransaction
89
from ttt.application.common.ports.uuids import UUIDs
910
from ttt.application.stars_purchase.ports.stars_purchase_log import (
@@ -26,8 +27,9 @@ class StartStarsPurchasePayment:
2627
payment_gateway: StarsPurchasePaymentGateway
2728
map_: Map
2829
log: StarsPurchaseLog
30+
retry: Retry
2931

30-
async def __call__(self, purchase_id: UUID, retry: bool) -> None: # noqa: FBT001
32+
async def __call__(self, purchase_id: UUID) -> None:
3133
"""
3234
:raises ttt.application.common.errors.serialization_error.SerializationError:
3335
""" # noqa: E501
@@ -57,7 +59,7 @@ async def __call__(self, purchase_id: UUID, retry: bool) -> None: # noqa: FBT00
5759
)
5860
await self.transaction.commit()
5961

60-
if retry:
62+
if self.retry:
6163
await self.payment_gateway.start_payment(payment_id)
6264
else:
6365
await self.payment_gateway.stop_payment_due_to_dublicate(

src/ttt/application/stars_purchase/start_stars_purchase_payment_completion.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
from dataclasses import dataclass
22
from uuid import UUID
33

4-
from ttt.application.common.ports.transaction import ReadonlyTransaction
5-
from ttt.application.stars_purchase.ports.stars_purchase_locks import (
6-
StarsPurchaseLocks,
7-
)
84
from ttt.application.stars_purchase.ports.stars_purchase_log import (
95
StarsPurchaseLog,
106
)
11-
from ttt.application.stars_purchase.ports.stars_purchase_payment_gateway import ( # noqa: E501
12-
StarsPurchasePaymentGateway,
13-
)
147
from ttt.application.stars_purchase.ports.stars_purchase_tasks import (
158
StarsPurchaseTasks,
169
)
@@ -23,10 +16,8 @@
2316
@dataclass(frozen=True, unsafe_hash=False)
2417
class StartStarsPurchasePaymentCompletion:
2518
tasks: StarsPurchaseTasks
26-
payment_gateway: StarsPurchasePaymentGateway
2719
views: StarsPurchaseViews
2820
log: StarsPurchaseLog
29-
locks: StarsPurchaseLocks
3021

3122
async def __call__(
3223
self,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from dataclasses import dataclass
2+
3+
from ttt.application.common.ports.retry import Retry
4+
from ttt.infrastructure.retrier import Retrier
5+
6+
7+
@dataclass
8+
class RetrierRetry(Retry):
9+
_retries: Retrier
10+
11+
def __bool__(self) -> bool:
12+
return self._retries.retry()

src/ttt/infrastructure/retrier.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from collections.abc import Callable
2+
from dataclasses import dataclass, field
3+
4+
5+
type MaxRetries = int
6+
type Retries = int
7+
8+
9+
@dataclass
10+
class Retrier:
11+
_max_retries_map: dict[type[BaseException], MaxRetries]
12+
13+
_retries_map: dict[type[BaseException], Retries] = field(
14+
init=False, default_factory=dict,
15+
)
16+
17+
def retry(self) -> bool:
18+
return bool(self._retries_map)
19+
20+
async def __call__[**PmT, RT](
21+
self,
22+
action: Callable[PmT, RT],
23+
*args: PmT.args,
24+
**kwargs: PmT.kwargs,
25+
) -> RT:
26+
while True:
27+
try:
28+
return action(*args, **kwargs)
29+
except BaseException as error:
30+
if type(error) not in self._max_retries_map:
31+
raise error from error
32+
33+
for error_type, max_retries in self._max_retries_map.items():
34+
if not isinstance(error, error_type):
35+
continue
36+
37+
if error_type not in self._retries_map:
38+
self._retries_map[error_type] = 0
39+
40+
self._retries_map[error_type] += 1
41+
42+
if self._retries_map[error_type] > max_retries:
43+
raise error from error

src/ttt/infrastructure/taskiq/tasks/complete_stars_purchase_payment_task.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
CompleteStarsPurchasePayment,
77
)
88
from ttt.entities.finance.payment.success import PaymentSuccess
9+
from ttt.infrastructure.retrier import Retrier
910
from ttt.infrastructure.taskiq.broker import NatsBroker
1011

1112

@@ -26,10 +27,9 @@ async def complete_stars_purchase_payment_task(
2627
payment_success_id: str,
2728
payment_success_gateway_id: str,
2829
complete_stars_purchase_payment: FromDishka[CompleteStarsPurchasePayment],
30+
retrier: FromDishka[Retrier],
2931
) -> None:
3032
payment_success = PaymentSuccess(
3133
payment_success_id, payment_success_gateway_id,
3234
)
33-
await complete_stars_purchase_payment(
34-
purchase_id, payment_success,
35-
)
35+
await retrier(complete_stars_purchase_payment, purchase_id, payment_success)

src/ttt/infrastructure/taskiq/tasks/make_ai_move_in_game_task.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from dishka.integrations.taskiq import FromDishka, inject
44

55
from ttt.application.game.game.make_ai_move_in_game import MakeAiMoveInGame
6+
from ttt.infrastructure.retrier import Retrier
67
from ttt.infrastructure.taskiq.broker import NatsBroker
78

89

@@ -21,5 +22,6 @@ async def make_ai_move_in_game_broker_task(
2122
game_id: UUID,
2223
ai_id: UUID,
2324
make_ai_move_in_game: FromDishka[MakeAiMoveInGame],
25+
retrier: FromDishka[Retrier],
2426
) -> None:
25-
await make_ai_move_in_game(user_id, game_id, ai_id)
27+
await retrier(make_ai_move_in_game, user_id, game_id, ai_id)

src/ttt/main/common/di.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
create_async_engine,
1414
)
1515

16+
from ttt.application.common.errors.serialization_error import SerializationError
1617
from ttt.application.common.ports.clock import Clock
1718
from ttt.application.common.ports.map import Map
1819
from ttt.application.common.ports.randoms import Randoms
20+
from ttt.application.common.ports.retry import Retry
1921
from ttt.application.common.ports.transaction import (
2022
NotSerializableTransaction,
2123
ReadonlyTransaction,
@@ -79,6 +81,7 @@
7981
TokenAsOriginalAdminToken,
8082
)
8183
from ttt.infrastructure.adapters.randoms import MersenneTwisterRandoms
84+
from ttt.infrastructure.adapters.retry import RetrierRetry
8285
from ttt.infrastructure.adapters.stars_purchase_log import (
8386
StructlogStarsPurchaseLog,
8487
)
@@ -104,6 +107,7 @@
104107
from ttt.infrastructure.openai.gemini import Gemini, gemini
105108
from ttt.infrastructure.pydantic_settings.envs import Envs
106109
from ttt.infrastructure.pydantic_settings.secrets import Secrets
110+
from ttt.infrastructure.retrier import Retrier
107111
from ttt.infrastructure.taskiq.broker import NatsBrokers
108112
from ttt.infrastructure.taskiq.tasks.complete_stars_purchase_payment_task import complete_stars_purchase_payment_broker
109113
from ttt.infrastructure.taskiq.tasks.make_ai_move_in_game_task import make_ai_move_in_game_broker
@@ -342,3 +346,9 @@ def provide_randoms(self) -> Randoms:
342346
provides=StarsPurchaseTasks,
343347
scope=Scope.APP,
344348
)
349+
350+
@provide(scope=Scope.REQUEST)
351+
def provide_retrier(self) -> Retrier:
352+
return Retrier(_max_retries_map={SerializationError: 10})
353+
354+
provide_retry = provide(RetrierRetry, provides=Retry, scope=Scope.REQUEST)

src/ttt/presentation/adapters/stars_purchase_payment_gateway.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,3 @@ async def stop_payment_due_to_error(self, payment_id: UUID) -> None:
5656
ok=False,
5757
error_message=message,
5858
)
59-
60-
61-
# INVOICE
62-
# OK?
63-
# | PAY | Dulicate

src/ttt/presentation/aiogram/user/routes/handle_payment.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
from dishka import AsyncContainer
44
from dishka.integrations.aiogram import inject
55

6-
from ttt.application.stars_purchase.dto.common import PaidStarsPurchasePayment
76
from ttt.application.stars_purchase.start_stars_purchase_payment_completion import ( # noqa: E501
87
StartStarsPurchasePaymentCompletion,
98
)
109
from ttt.entities.finance.payment.success import PaymentSuccess
1110
from ttt.entities.tools.assertion import not_none
11+
from ttt.infrastructure.retrier import Retrier
1212
from ttt.presentation.aiogram.user.invoices import (
1313
StarsPurchaseInvoicePayload,
1414
invoce_payload_adapter,
@@ -36,12 +36,13 @@ async def _(
3636

3737
match invoce_payload:
3838
case StarsPurchaseInvoicePayload():
39+
retrier = await dishka_container.get(Retrier)
3940
start_stars_purchase_payment_completion = (
4041
await dishka_container.get(StartStarsPurchasePaymentCompletion)
4142
)
42-
payment = PaidStarsPurchasePayment(
43-
invoce_payload.purchase_id,
43+
await retrier(
44+
start_stars_purchase_payment_completion,
4445
invoce_payload.user_id,
46+
invoce_payload.purchase_id,
4547
success,
4648
)
47-
await start_stars_purchase_payment_completion(payment)

0 commit comments

Comments
 (0)