35
35
from denokv ._pycompat .typing import Any
36
36
from denokv ._pycompat .typing import ClassVar
37
37
from denokv ._pycompat .typing import Container
38
+ from denokv ._pycompat .typing import Final
38
39
from denokv ._pycompat .typing import Generic
40
+ from denokv ._pycompat .typing import Iterable
39
41
from denokv ._pycompat .typing import Mapping
40
42
from denokv ._pycompat .typing import MutableSequence
41
43
from denokv ._pycompat .typing import Never
60
62
from denokv .datapath import AnyKvKey
61
63
from denokv .datapath import CheckFailure
62
64
from denokv .datapath import pack_key
65
+ from denokv .errors import DenoKvError
63
66
from denokv .kv_keys import KvKey
64
67
from denokv .result import AnyFailure
65
68
from denokv .result import AnySuccess
@@ -1330,65 +1333,126 @@ def _enqueue(self, enqueue: Enqueue, /) -> Self:
1330
1333
return self
1331
1334
1332
1335
1333
- @dataclass (init = False , unsafe_hash = True , ** slots_if310 ())
1334
- class ConflictedWrite (FrozenAfterInitDataclass , AnyFailure ):
1336
+ EMPTY_MAP : Final [Mapping [Any , Any ]] = MappingProxyType ({})
1337
+
1338
+
1339
+ # TODO: Support capturing retries in the FailedWrite/CommittedWrite?
1340
+ @dataclass (init = False , unsafe_hash = True )
1341
+ class FailedWrite (FrozenAfterInitDataclass , AnyFailure , DenoKvError ):
1335
1342
if TYPE_CHECKING :
1336
1343
1337
1344
def _AnyFailure_marker (self , no_call : Never ) -> Never : ...
1338
1345
1339
- ok : Literal [ False ]
1340
- conflicts : Mapping [ AnyKvKey , CheckRepresentation ]
1341
- versionstamp : None
1342
- checks : Sequence [CheckRepresentation ]
1343
- mutations : Sequence [ MutationRepresentation ]
1344
- enqueues : Sequence [ EnqueueRepresentation ]
1345
- endpoint : EndpointInfo
1346
+ checks : Final [ Sequence [ CheckRepresentation ]] = field ()
1347
+ failed_checks : Final [ Sequence [ int ]] = field ()
1348
+ mutations : Final [ Sequence [ MutationRepresentation ]] = field ()
1349
+ enqueues : Final [ Sequence [EnqueueRepresentation ]] = field ()
1350
+ endpoint : Final [ EndpointInfo ] = field ()
1351
+ ok : Final [ Literal [ False ]] = False # noqa: PYI064
1352
+ versionstamp : Final [ None ] = None
1346
1353
1347
1354
def __init__ (
1348
1355
self ,
1349
- failed_checks : Sequence [int ],
1350
- checks : Sequence [CheckRepresentation ],
1351
- mutations : Sequence [MutationRepresentation ],
1352
- enqueues : Sequence [EnqueueRepresentation ],
1356
+ checks : Iterable [CheckRepresentation ],
1357
+ mutations : Iterable [MutationRepresentation ],
1358
+ enqueues : Iterable [EnqueueRepresentation ],
1353
1359
endpoint : EndpointInfo ,
1360
+ * ,
1361
+ cause : BaseException | None = None ,
1354
1362
) -> None :
1355
- self .ok = False
1356
- try :
1357
- self .conflicts = MappingProxyType (
1358
- {checks [i ].key : checks [i ] for i in failed_checks }
1359
- )
1360
- except IndexError as e :
1361
- raise ValueError ("failed_checks contains out-of-bounds index" ) from e
1362
- self .versionstamp = None
1363
- self .checks = tuple (checks )
1364
- self .mutations = tuple (mutations )
1365
- self .enqueues = tuple (enqueues )
1366
- self .endpoint = endpoint
1363
+ super (FailedWrite , self ).__init__ ()
1364
+ self .checks = tuple (checks ) # type: ignore[misc] # Cannot assign to final
1365
+ # Allow subclass to initialise failed_checks
1366
+ if not hasattr (self , "failed_checks" ):
1367
+ self .failed_checks = tuple () # type: ignore[misc] # Cannot assign to final
1368
+ self .mutations = tuple (mutations ) # type: ignore[misc] # Cannot assign to final
1369
+ self .enqueues = tuple (enqueues ) # type: ignore[misc] # Cannot assign to final
1370
+ self .endpoint = endpoint # type: ignore[misc] # Cannot assign to final
1371
+ self .__cause__ = cause
1372
+
1373
+ @property
1374
+ def conflicts (self ) -> Mapping [AnyKvKey , CheckRepresentation ]:
1375
+ checks = self .checks
1376
+ return {checks [i ].key : checks [i ] for i in self .failed_checks }
1377
+
1378
+ def _get_cause_description (self ) -> str :
1379
+ if self .__cause__ :
1380
+ return type (self .__cause__ ).__name__
1381
+ return "unspecified cause"
1382
+
1383
+ @property
1384
+ def message (self ) -> str :
1385
+ # TODO: after xxx attempts?
1386
+ return (
1387
+ f"to { str (self .endpoint .url )!r} "
1388
+ f"due to { self ._get_cause_description ()} , "
1389
+ f"with { len (self .checks )} checks, "
1390
+ f"{ len (self .mutations )} mutations, "
1391
+ f"{ len (self .enqueues )} enqueues"
1392
+ )
1393
+
1394
+ def __str__ (self ) -> str :
1395
+ return f"Write failed { self .message } "
1367
1396
1368
1397
def __repr__ (self ) -> str :
1398
+ return f"<{ type (self ).__name__ } { self .message } >"
1399
+
1400
+
1401
+ def _normalise_failed_checks (
1402
+ failed_checks : Iterable [int ], check_count : int
1403
+ ) -> tuple [int , ...]:
1404
+ failed_checks = tuple (sorted (failed_checks ))
1405
+ if failed_checks and (failed_checks [0 ] < 0 or failed_checks [- 1 ] >= check_count ):
1406
+ raise ValueError ("failed_checks contains out-of-bounds index" )
1407
+ return failed_checks
1408
+
1409
+
1410
+ class ConflictedWrite (FailedWrite ):
1411
+ def __init__ (
1412
+ self ,
1413
+ failed_checks : Iterable [int ],
1414
+ checks : Iterable [CheckRepresentation ],
1415
+ mutations : Iterable [MutationRepresentation ],
1416
+ enqueues : Iterable [EnqueueRepresentation ],
1417
+ endpoint : EndpointInfo ,
1418
+ * ,
1419
+ cause : BaseException | None = None ,
1420
+ ) -> None :
1421
+ _checks = tuple (checks )
1422
+ self .failed_checks = _normalise_failed_checks ( # type: ignore[misc] # Cannot assign to final attribute "failed_checks"
1423
+ failed_checks ,
1424
+ check_count = len (_checks ),
1425
+ )
1426
+ super (ConflictedWrite , self ).__init__ (
1427
+ _checks , mutations , enqueues , endpoint , cause = cause
1428
+ )
1429
+
1430
+ @property
1431
+ def message (self ) -> str :
1369
1432
return (
1370
- f"<{ type (self ).__name__ } "
1371
1433
f"NOT APPLIED to { str (self .endpoint .url )!r} with "
1372
1434
f"{ len (self .conflicts )} /{ len (self .checks )} checks CONFLICTING, "
1373
1435
f"{ len (self .mutations )} mutations, "
1374
1436
f"{ len (self .enqueues )} enqueues"
1375
- f">"
1376
1437
)
1377
1438
1439
+ def __str__ (self ) -> str :
1440
+ return f"Write { self .message } "
1441
+
1378
1442
1379
1443
@dataclass (init = False , unsafe_hash = True , ** slots_if310 ())
1380
1444
class CommittedWrite (FrozenAfterInitDataclass , AnySuccess ):
1381
1445
if TYPE_CHECKING :
1382
1446
1383
1447
def _AnySuccess_marker (self , no_call : Never ) -> Never : ...
1384
1448
1385
- ok : Literal [True ]
1386
- conflicts : Mapping [KvKey , CheckRepresentation ] # empty
1387
- versionstamp : VersionStamp
1388
- checks : Sequence [CheckRepresentation ]
1389
- mutations : Sequence [MutationRepresentation ]
1390
- enqueues : Sequence [EnqueueRepresentation ]
1391
- endpoint : EndpointInfo
1449
+ ok : Final [ Literal [True ]] # noqa: PYI064
1450
+ conflicts : Final [ Mapping [KvKey , CheckRepresentation ] ] # empty
1451
+ versionstamp : Final [ VersionStamp ]
1452
+ checks : Final [ Sequence [CheckRepresentation ] ]
1453
+ mutations : Final [ Sequence [MutationRepresentation ] ]
1454
+ enqueues : Final [ Sequence [EnqueueRepresentation ] ]
1455
+ endpoint : Final [ EndpointInfo ]
1392
1456
1393
1457
def __init__ (
1394
1458
self ,
@@ -1399,23 +1463,28 @@ def __init__(
1399
1463
endpoint : EndpointInfo ,
1400
1464
) -> None :
1401
1465
self .ok = True
1402
- self .conflicts = MappingProxyType ({})
1466
+ self .conflicts = EMPTY_MAP
1403
1467
self .versionstamp = versionstamp
1404
1468
self .checks = tuple (checks )
1405
1469
self .mutations = tuple (mutations )
1406
1470
self .enqueues = tuple (enqueues )
1407
1471
self .endpoint = endpoint
1408
1472
1409
- def __repr__ (self ) -> str :
1473
+ @property
1474
+ def _message (self ) -> str :
1410
1475
return (
1411
- f"<{ type (self ).__name__ } "
1412
1476
f"version 0x{ self .versionstamp } to { str (self .endpoint .url )!r} with "
1413
1477
f"{ len (self .checks )} checks, "
1414
1478
f"{ len (self .mutations )} mutations, "
1415
1479
f"{ len (self .enqueues )} enqueues"
1416
- f">"
1417
1480
)
1418
1481
1482
+ def __str__ (self ) -> str :
1483
+ return f"Write committed { self ._message } "
1484
+
1485
+ def __repr__ (self ) -> str :
1486
+ return f"<{ type (self ).__name__ } { self ._message } >"
1487
+
1419
1488
1420
1489
CompletedWrite : TypeAlias = Union [CommittedWrite , ConflictedWrite ]
1421
1490
@@ -2078,4 +2147,6 @@ def _evaluate_backoff_schedule(self) -> Sequence[int]:
2078
2147
return [int (delay * 1000 ) for delay in delay_seconds ]
2079
2148
2080
2149
2081
- WriteOperation : TypeAlias = Union [Check , Set , Sum , Min , Max , Delete , Enqueue ]
2150
+ WriteOperation : TypeAlias = Union [
2151
+ CheckRepresentation , MutationRepresentation , EnqueueRepresentation
2152
+ ]
0 commit comments