Skip to content

Commit f76d61a

Browse files
committed
feat: make it better, faster, stronger
1 parent dabdb4d commit f76d61a

29 files changed

Lines changed: 657 additions & 268 deletions

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ dev = [
2727
"mypy[faster-cache]==1.15.0",
2828
"ruff==0.9.7",
2929
"pytest==8.3.4",
30-
"pytest-asyncio==0.25.3",
3130
"pytest-cov==6.0.0",
31+
"pytest-asyncio==0.25.3",
32+
"pytest-timeout==2.4.0",
3233
"dirty-equals==0.8.0",
3334
"httpx==0.27.2",
3435
"httpx-ws==0.7.2",
35-
"pytest-timeout==2.4.0",
36+
"plotext==5.2.8",
3637
]
3738

3839
[build-system]

src/tgdb/application/horizon/start_transaction.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from tgdb.application.common.ports.shared_horizon import SharedHorizon
55
from tgdb.application.common.ports.uuids import UUIDs
66
from tgdb.entities.horizon.transaction import (
7+
XID,
78
IsolationLevel,
89
)
910

@@ -14,9 +15,13 @@ class StartTransaction:
1415
shared_horizon: SharedHorizon
1516
clock: Clock
1617

17-
async def __call__(self, isolation_level: IsolationLevel) -> None:
18+
async def __call__(self, isolation_level: IsolationLevel) -> XID:
19+
"""
20+
:raises tgdb.entities.horizon.horizon.InvalidTransactionStateError:
21+
"""
22+
1823
time = await self.clock
1924
xid = await self.uuids.random_uuid()
2025

2126
async with self.shared_horizon as horizon:
22-
horizon.start_transaction(time, xid, isolation_level)
27+
return horizon.start_transaction(time, xid, isolation_level)

src/tgdb/application/view_tuples.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from collections.abc import Sequence
12
from dataclasses import dataclass
23

34
from tgdb.application.common.ports.clock import Clock
@@ -7,6 +8,7 @@
78
from tgdb.entities.horizon.transaction import XID
89
from tgdb.entities.numeration.number import Number
910
from tgdb.entities.relation.scalar import Scalar
11+
from tgdb.entities.relation.tuple import Tuple
1012
from tgdb.entities.relation.tuple_effect import viewed_tuple
1113
from tgdb.entities.relation.versioned_tuple import versioned_tuple
1214

@@ -24,7 +26,7 @@ async def __call__(
2426
relation_number: Number,
2527
attribute_number: Number,
2628
scalar: Scalar,
27-
) -> None:
29+
) -> Sequence[Tuple]:
2830
"""
2931
:raises tgdb.application.common.ports.relations.NoRelationError:
3032
:raises tgdb.entities.horizon.horizon.NoTransactionError:
@@ -46,3 +48,5 @@ async def __call__(
4648
for viewed_tuple_ in viewed_tuples:
4749
time = await self.clock
4850
horizon.include(time, xid, viewed_tuple_)
51+
52+
return tuples

src/tgdb/entities/__init__.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +0,0 @@
1-
from time import monotonic_ns
2-
3-
4-
n = 100_000
5-
6-
x = monotonic_ns()
7-
frozenset(range(n)) & frozenset(range(n * 2))
8-
print(monotonic_ns() - x)
9-
10-
11-
# 1 000 000 000

src/tgdb/entities/horizon/horizon.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def start_transaction(
8787
"""
8888

8989
assert_(
90-
xid not in chain(*self._transaction_maps()),
90+
all(xid not in map for map in self._transaction_maps()),
9191
else_=InvalidTransactionStateError,
9292
)
9393

src/tgdb/entities/relation/domain.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def __contains__(self, scalar: Scalar) -> bool:
5555

5656

5757
@dataclass(frozen=True)
58-
class SetDomain[T: bool | int | str | datetime | UUID](Domain):
58+
class SetDomain[T: int | str | datetime | UUID](Domain):
5959
values: tuple[T, ...]
6060
_type: type[T]
6161
_is_nonable: bool

src/tgdb/infrastructure/async_map.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from asyncio import Future
2-
from collections.abc import Awaitable
2+
from collections.abc import Awaitable, Iterator
33
from dataclasses import dataclass, field
44

55

@@ -9,6 +9,12 @@ class AsyncMap[KeyT, ValueT]:
99
init=False, default_factory=dict
1010
)
1111

12+
def __iter__(self) -> Iterator[KeyT]:
13+
return iter(self._map)
14+
15+
def __len__(self) -> int:
16+
return len(self._map)
17+
1218
def __getitem__(self, key: KeyT) -> Awaitable[ValueT]:
1319
if key in self._map:
1420
return self._map[key]
@@ -20,6 +26,9 @@ def __getitem__(self, key: KeyT) -> Awaitable[ValueT]:
2026

2127
def __setitem__(self, key: KeyT, result: ValueT) -> None:
2228
if key not in self._map:
29+
futute_result = Future[ValueT]()
30+
futute_result.set_result(result)
31+
self._map[key] = futute_result
2332
return
2433

2534
futute_result = self._map[key]
Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,54 @@
11
from collections import OrderedDict
22
from collections.abc import Awaitable, Callable
3-
from contextlib import suppress
43
from dataclasses import dataclass, field
4+
from typing import ClassVar
5+
6+
7+
class NoExternalValue:
8+
_instance: "ClassVar[NoExternalValue | None]" = None
9+
10+
def __new__(cls) -> "NoExternalValue":
11+
if NoExternalValue._instance is not None:
12+
return NoExternalValue._instance
13+
14+
instance = super().__new__(cls)
15+
NoExternalValue._instance = instance
16+
17+
return instance
18+
19+
20+
type ExternalValue[ValueT] = ValueT | NoExternalValue
521

622

723
@dataclass(frozen=True, unsafe_hash=False)
824
class LazyMap[KeyT, ValueT]:
9-
_computed_map_max_len: int
10-
_external_value: Callable[[KeyT], Awaitable[ValueT | None]]
11-
_computed_map: OrderedDict[KeyT, ValueT] = field(
25+
_cache_map_max_len: int
26+
_external_value: Callable[[KeyT], Awaitable[ExternalValue[ValueT]]]
27+
28+
_cache_map: OrderedDict[KeyT, ExternalValue[ValueT]] = field(
1229
init=False, default_factory=OrderedDict
1330
)
1431

15-
async def __getitem__(self, key: KeyT) -> ValueT | None:
16-
with suppress(KeyError):
17-
return self._computed_map[key]
32+
def cache_map(self) -> OrderedDict[KeyT, ExternalValue[ValueT]]:
33+
return OrderedDict(self._cache_map)
1834

19-
value = await self._external_value(key)
35+
async def __getitem__(self, key: KeyT) -> ValueT:
36+
if key in self._cache_map:
37+
return self._output(self._cache_map[key])
2038

21-
if value is None:
22-
return None
39+
value = await self._external_value(key)
40+
self._cache_map[key] = value
2341

24-
self._computed_map[key] = value
42+
if len(self._cache_map) > self._cache_map_max_len:
43+
self._cache_map.pop(next(iter(self._cache_map)))
2544

26-
if len(self._computed_map) > self._computed_map_max_len:
27-
self._computed_map.pop(next(iter(self._computed_map)))
28-
29-
return value
45+
return self._output(value)
3046

3147
def __setitem__(self, key: KeyT, value: ValueT) -> None:
32-
self._computed_map[key] = value
48+
self._cache_map[key] = value
49+
50+
def _output(self, value: ExternalValue[ValueT]) -> ValueT:
51+
if isinstance(value, NoExternalValue):
52+
raise KeyError
3353

34-
def __delitem__(self, key: KeyT) -> None:
35-
del self._computed_map[key]
54+
return value
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from fastapi import FastAPI, Response, status
2+
from fastapi.responses import JSONResponse
3+
4+
from tgdb.entities.horizon.horizon import (
5+
InvalidTransactionStateError,
6+
NoTransactionError,
7+
)
8+
from tgdb.entities.horizon.transaction import (
9+
ConflictError,
10+
NonSerializableWriteTransactionError,
11+
)
12+
from tgdb.presentation.fastapi.schemas.errors import (
13+
InvalidTransactionStateSchema,
14+
NoTransactionSchema,
15+
TransactionConflictSchema,
16+
)
17+
18+
19+
def add_error_handling(app: FastAPI) -> None:
20+
@app.exception_handler(ConflictError)
21+
def _(conflict: ConflictError) -> Response:
22+
schema = TransactionConflictSchema.of(conflict)
23+
24+
return JSONResponse(
25+
schema.model_dump(mode="json", by_alias=True),
26+
status_code=status.HTTP_409_CONFLICT,
27+
)
28+
29+
@app.exception_handler(NoTransactionError)
30+
def _(_: object) -> Response:
31+
return JSONResponse(
32+
NoTransactionSchema().model_dump(mode="json", by_alias=True),
33+
status_code=status.HTTP_404_NOT_FOUND,
34+
)
35+
36+
@app.exception_handler(InvalidTransactionStateError)
37+
def _(_: object) -> Response:
38+
schema = InvalidTransactionStateSchema()
39+
return JSONResponse(
40+
schema.model_dump(mode="json", by_alias=True),
41+
status_code=status.HTTP_404_NOT_FOUND,
42+
)
43+
44+
@app.exception_handler(NonSerializableWriteTransactionError)
45+
def _(_: object) -> Response:
46+
schema = InvalidTransactionStateSchema()
47+
return JSONResponse(
48+
schema.model_dump(mode="json", by_alias=True),
49+
status_code=status.HTTP_404_NOT_FOUND,
50+
)

src/tgdb/presentation/fastapi/routers.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from tgdb.presentation.fastapi.routes.commit_transaction import (
22
commit_transaction_router,
33
)
4+
from tgdb.presentation.fastapi.routes.healthcheck import healthcheck_router
45
from tgdb.presentation.fastapi.routes.rollback_transaction import (
56
rollback_transaction_router,
67
)
@@ -9,6 +10,13 @@
910
)
1011

1112

13+
_monitoring_routers = (
14+
healthcheck_router,
15+
rollback_transaction_router,
16+
commit_transaction_router,
17+
)
18+
19+
1220
_transaction_routers = (
1321
start_transaction_router,
1422
rollback_transaction_router,
@@ -17,5 +25,6 @@
1725

1826

1927
all_routers = (
28+
*_monitoring_routers,
2029
*_transaction_routers,
2130
)

0 commit comments

Comments
 (0)