Skip to content

Commit 69243bc

Browse files
committed
chore(infrastructure): replace pickle to pydantic
1 parent 7cf4a81 commit 69243bc

13 files changed

Lines changed: 453 additions & 27 deletions

File tree

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ lines-after-imports = 2
8282
"src/tgdb/entities/*" = ["PLR2004"]
8383
"src/tgdb/application/*" = ["PLR0917"]
8484
"src/tgdb/infrastructure/adapters/*" = ["RUF029"]
85-
"src/tgdb/infrastructure/*" = ["S403", "S301"]
8685
"tests/*" = ["PLR0124", "PLR0917", "S106", "C901", "PLR2004"]
8786
"__init__.py" = ["PLC0414"]
8887

src/tgdb/infrastructure/adapters/buffer.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
import pickle
21
from asyncio import Event, wait_for
32
from collections import deque
43
from collections.abc import AsyncIterator, Sequence
54
from dataclasses import dataclass, field
65
from types import TracebackType
7-
from typing import Self
6+
from typing import ClassVar, Self
7+
8+
from pydantic import TypeAdapter
89

910
from tgdb.application.common.ports.buffer import Buffer
1011
from tgdb.entities.horizon.transaction import Commit, PreparedCommit
12+
from tgdb.infrastructure.pydantic.horizon.commit import (
13+
EncodableCommit,
14+
EncodablePreparedCommit,
15+
)
1116
from tgdb.infrastructure.telethon.in_telegram_bytes import InTelegramBytes
1217

1318

@@ -51,22 +56,20 @@ class InTelegramReplicablePreparedCommitBuffer(Buffer[Commit | PreparedCommit]):
5156
_buffer: Buffer[Commit | PreparedCommit]
5257
_in_tg_encoded_commits: InTelegramBytes
5358

59+
_adapter: ClassVar = TypeAdapter(
60+
tuple[EncodableCommit | EncodablePreparedCommit, ...]
61+
)
62+
5463
async def __aenter__(self) -> Self:
5564
encoded_commits = await self._in_tg_encoded_commits
5665

5766
if encoded_commits is None:
5867
return self
5968

60-
commits = pickle.loads(encoded_commits)
61-
62-
if not isinstance(commits, list):
63-
raise TypeError(str(commits))
69+
encodable_commits = self._adapter.validate_json(encoded_commits)
6470

65-
for commit in commits:
66-
if not isinstance(commit, PreparedCommit):
67-
raise TypeError(str(commits))
68-
69-
await self._buffer.add(commit)
71+
for commit in encodable_commits:
72+
await self._buffer.add(commit.entity())
7073

7174
return self
7275

@@ -84,7 +87,18 @@ async def __aiter__(
8487
self,
8588
) -> AsyncIterator[Sequence[Commit | PreparedCommit]]:
8689
async for commits in self._buffer:
87-
encoded_commits = pickle.dumps(list(commits))
90+
encodable_commits = tuple(map(self._encodable_commit, commits))
91+
encoded_commits = self._adapter.dump_json(encodable_commits)
8892
await self._in_tg_encoded_commits.set(encoded_commits)
8993

9094
yield commits
95+
96+
def _encodable_commit(
97+
self, commit: Commit | PreparedCommit
98+
) -> EncodableCommit | EncodablePreparedCommit:
99+
match commit:
100+
case Commit():
101+
return EncodableCommit.of(commit)
102+
103+
case PreparedCommit():
104+
return EncodablePreparedCommit.of(commit)

src/tgdb/infrastructure/adapters/relations.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import pickle
21
from dataclasses import dataclass
32
from types import TracebackType
4-
from typing import Self
3+
from typing import ClassVar, Self
54

65
from in_memory_db import InMemoryDb
6+
from pydantic import TypeAdapter
77

88
from tgdb.application.relation.ports.relations import (
99
NoRelationError,
@@ -12,6 +12,7 @@
1212
)
1313
from tgdb.entities.numeration.number import Number
1414
from tgdb.entities.relation.relation import Relation
15+
from tgdb.infrastructure.pydantic.relation.relation import EncodableRelation
1516
from tgdb.infrastructure.telethon.in_telegram_bytes import InTelegramBytes
1617

1718

@@ -44,6 +45,8 @@ class InTelegramReplicableRelations(Relations):
4445
_in_tg_encoded_relations: InTelegramBytes
4546
_cached_relations: InMemoryDb[Relation]
4647

48+
_adapter: ClassVar = TypeAdapter(tuple[EncodableRelation, ...])
49+
4750
async def __aenter__(self) -> Self:
4851
for loaded_relation in await self._loaded_relations():
4952
self._cached_relations.insert(loaded_relation)
@@ -88,22 +91,17 @@ async def add(self, relation: Relation) -> None:
8891

8992
self._cached_relations.insert(relation)
9093

91-
encoded_cached_relations = pickle.dumps(tuple(self._cached_relations))
92-
await self._in_tg_encoded_relations.set(encoded_cached_relations)
94+
encodable_relations = (
95+
tuple(map(EncodableRelation.of, self._cached_relations))
96+
)
97+
encoded_relations = self._adapter.dump_json(encodable_relations, by_alias=True)
98+
await self._in_tg_encoded_relations.set(encoded_relations)
9399

94100
async def _loaded_relations(self) -> tuple[Relation, ...]:
95101
encoded_relations = await self._in_tg_encoded_relations
96102

97103
if encoded_relations is None:
98104
return tuple()
99105

100-
relations = pickle.loads(encoded_relations)
101-
102-
if not isinstance(relations, tuple):
103-
raise TypeError(relations)
104-
105-
for relation in relations:
106-
if not isinstance(relation, Relation):
107-
raise TypeError(relation)
108-
109-
return relations
106+
encodable_relations = self._adapter.validate_json(encoded_relations)
107+
return tuple(it.entity() for it in encodable_relations)

src/tgdb/infrastructure/pydantic/__init__.py

Whitespace-only changes.

src/tgdb/infrastructure/pydantic/horizon/__init__.py

Whitespace-only changes.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from typing import Literal
2+
3+
from pydantic import BaseModel
4+
5+
from tgdb.entities.horizon.transaction import XID, Commit, PreparedCommit
6+
from tgdb.infrastructure.pydantic.horizon.transaction_effect import (
7+
EncodableTransactionScalarEffect,
8+
encodable_transaction_scalar_effect,
9+
)
10+
11+
12+
class EncodableCommit(BaseModel):
13+
type: Literal["commit"] = "commit"
14+
xid: XID
15+
effect: tuple[EncodableTransactionScalarEffect, ...]
16+
17+
def entity(self) -> Commit:
18+
return Commit(
19+
self.xid,
20+
frozenset(it.entity() for it in self.effect)
21+
)
22+
23+
@classmethod
24+
def of(cls, entity: Commit) -> "EncodableCommit":
25+
return EncodableCommit(
26+
xid=entity.xid,
27+
effect=tuple(
28+
map(encodable_transaction_scalar_effect, entity.effect)
29+
),
30+
)
31+
32+
33+
class EncodablePreparedCommit(BaseModel):
34+
type: Literal["prepared_commit"] = "prepared_commit"
35+
xid: XID
36+
effect: tuple[EncodableTransactionScalarEffect, ...]
37+
38+
def entity(self) -> Commit:
39+
return Commit(
40+
self.xid,
41+
frozenset(it.entity() for it in self.effect)
42+
)
43+
44+
@classmethod
45+
def of(cls, entity: PreparedCommit) -> "EncodablePreparedCommit":
46+
return EncodablePreparedCommit(
47+
xid=entity.xid,
48+
effect=tuple(
49+
map(encodable_transaction_scalar_effect, entity.effect)
50+
),
51+
)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from tgdb.entities.horizon.transaction import TransactionScalarEffect
2+
from tgdb.entities.relation.tuple_effect import (
3+
DeletedTuple,
4+
MigratedTuple,
5+
MutatedTuple,
6+
NewTuple,
7+
)
8+
from tgdb.infrastructure.pydantic.relation.tuple_effect import (
9+
EncodableDeletedTuple,
10+
EncodableMigratedTuple,
11+
EncodableMutatedTuple,
12+
EncodableNewTuple,
13+
)
14+
15+
16+
type EncodableTransactionScalarEffect = (
17+
EncodableNewTuple
18+
| EncodableMutatedTuple
19+
| EncodableMigratedTuple
20+
| EncodableDeletedTuple
21+
)
22+
23+
24+
def encodable_transaction_scalar_effect(
25+
effect: TransactionScalarEffect
26+
) -> EncodableTransactionScalarEffect:
27+
match effect:
28+
case NewTuple():
29+
return EncodableNewTuple.of(effect)
30+
31+
case MutatedTuple():
32+
return EncodableMutatedTuple.of(effect)
33+
34+
case MigratedTuple():
35+
return EncodableMigratedTuple.of(effect)
36+
37+
case DeletedTuple():
38+
return EncodableDeletedTuple.of(effect)

src/tgdb/infrastructure/pydantic/relation/__init__.py

Whitespace-only changes.
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
from typing import Literal
2+
3+
from pydantic import BaseModel, Field
4+
5+
from tgdb.entities.relation.domain import (
6+
BoolDomain,
7+
DatetimeDomain,
8+
Domain,
9+
IntDomain,
10+
SetDomain,
11+
StrDomain,
12+
UuidDomain,
13+
)
14+
from tgdb.entities.relation.scalar import Scalar
15+
16+
17+
class EncodableIntDomain(BaseModel):
18+
type: Literal["int"] = "int"
19+
min: int
20+
max: int
21+
is_nonable: bool
22+
23+
@classmethod
24+
def of(cls, domain: "IntDomain") -> "EncodableIntDomain":
25+
return cls(min=domain.min, max=domain.max, is_nonable=domain.is_nonable)
26+
27+
def entity(self) -> "IntDomain":
28+
return IntDomain(self.min, self.max, self.is_nonable)
29+
30+
31+
class EncodableStrDomain(BaseModel):
32+
type: Literal["str"] = "str"
33+
max_len: int
34+
is_nonable: bool
35+
36+
@classmethod
37+
def of(cls, domain: "StrDomain") -> "EncodableStrDomain":
38+
return cls(
39+
max_len=domain.max_len,
40+
is_nonable=domain.is_nonable,
41+
)
42+
43+
def entity(self) -> "StrDomain":
44+
return StrDomain(self.max_len, self.is_nonable)
45+
46+
47+
class EncodableBoolDomain(BaseModel):
48+
type: Literal["bool"] = "bool"
49+
is_nonable: bool
50+
51+
@classmethod
52+
def of(cls, domain: BoolDomain) -> "EncodableBoolDomain":
53+
return cls(is_nonable=domain.is_nonable)
54+
55+
def entity(self) -> BoolDomain:
56+
return BoolDomain(self.is_nonable)
57+
58+
59+
class EncodableTimeDomain(BaseModel):
60+
type: Literal["time"] = "time"
61+
is_nonable: bool
62+
63+
@classmethod
64+
def of(cls, domain: DatetimeDomain) -> "EncodableTimeDomain":
65+
return cls(is_nonable=domain.is_nonable)
66+
67+
def entity(self) -> DatetimeDomain:
68+
return DatetimeDomain(self.is_nonable)
69+
70+
71+
class EncodableUuidDomain(BaseModel):
72+
type: Literal["uuid"] = "uuid"
73+
is_nonable: bool
74+
75+
@classmethod
76+
def of(cls, domain: UuidDomain) -> "EncodableUuidDomain":
77+
return cls(is_nonable=domain.is_nonable)
78+
79+
def entity(self) -> UuidDomain:
80+
return UuidDomain(self.is_nonable)
81+
82+
83+
class EncodableSetDomain(BaseModel):
84+
type: Literal["set"] = "set"
85+
scalars: tuple[Scalar, ...]
86+
87+
@classmethod
88+
def of(cls, domain: SetDomain) -> "EncodableSetDomain":
89+
return cls(scalars=domain)
90+
91+
def entity(self) -> SetDomain:
92+
return self.scalars
93+
94+
95+
type EncodableDomain = (
96+
EncodableBoolDomain
97+
| EncodableIntDomain
98+
| EncodableStrDomain
99+
| EncodableTimeDomain
100+
| EncodableUuidDomain
101+
| EncodableSetDomain
102+
)
103+
104+
105+
def encodable_domain(domain: Domain) -> EncodableDomain:
106+
match domain:
107+
case IntDomain():
108+
return EncodableIntDomain.of(domain)
109+
110+
case StrDomain():
111+
return EncodableStrDomain.of(domain)
112+
113+
case BoolDomain():
114+
return EncodableBoolDomain.of(domain)
115+
116+
case UuidDomain():
117+
return EncodableUuidDomain.of(domain)
118+
119+
case DatetimeDomain():
120+
return EncodableTimeDomain.of(domain)
121+
122+
case tuple():
123+
return EncodableSetDomain.of(domain)

0 commit comments

Comments
 (0)