spakky-data 4.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spakky_data-4.0.0/PKG-INFO +141 -0
- spakky_data-4.0.0/README.md +131 -0
- spakky_data-4.0.0/pyproject.toml +63 -0
- spakky_data-4.0.0/src/spakky/data/__init__.py +40 -0
- spakky_data-4.0.0/src/spakky/data/external/__init__.py +17 -0
- spakky_data-4.0.0/src/spakky/data/external/error.py +6 -0
- spakky_data-4.0.0/src/spakky/data/external/proxy.py +55 -0
- spakky_data-4.0.0/src/spakky/data/persistency/__init__.py +28 -0
- spakky_data-4.0.0/src/spakky/data/persistency/error.py +6 -0
- spakky_data-4.0.0/src/spakky/data/persistency/repository.py +83 -0
- spakky_data-4.0.0/src/spakky/data/persistency/transaction.py +93 -0
- spakky_data-4.0.0/src/spakky/data/stereotype/__init__.py +0 -0
- spakky_data-4.0.0/src/spakky/data/stereotype/repository.py +20 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: spakky-data
|
|
3
|
+
Version: 4.0.0
|
|
4
|
+
Summary: Data access layer for Spakky Framework (Repository implementations, ORM integration)
|
|
5
|
+
Author: Spakky
|
|
6
|
+
Author-email: Spakky <sejong418@icloud.com>
|
|
7
|
+
Requires-Dist: spakky-domain>=4.0.0
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# Spakky Data
|
|
12
|
+
|
|
13
|
+
Data access layer abstractions for [Spakky Framework](https://github.com/E5presso/spakky-framework).
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install spakky-data
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **Repository Pattern**: Generic repository interfaces for aggregate persistence
|
|
24
|
+
- **Transaction Management**: Abstract transaction classes with autocommit support
|
|
25
|
+
- **External Proxy**: Proxy pattern for external service data access
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### Repository Pattern
|
|
30
|
+
|
|
31
|
+
Define repository interfaces for your domain aggregates:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from abc import abstractmethod
|
|
35
|
+
from uuid import UUID
|
|
36
|
+
|
|
37
|
+
from spakky.data.persistency.repository import IAsyncGenericRepository
|
|
38
|
+
from spakky.domain.models.aggregate_root import AbstractAggregateRoot
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class User(AbstractAggregateRoot[UUID]):
|
|
42
|
+
name: str
|
|
43
|
+
email: str
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class IUserRepository(IAsyncGenericRepository[User, UUID]):
|
|
47
|
+
@abstractmethod
|
|
48
|
+
async def find_by_email(self, email: str) -> User | None: ...
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Transaction Management
|
|
52
|
+
|
|
53
|
+
Use abstract transactions for database operations:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from spakky.data.persistency.transaction import AbstractAsyncTransaction
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SQLAlchemyTransaction(AbstractAsyncTransaction):
|
|
60
|
+
def __init__(self, session_factory, autocommit: bool = True) -> None:
|
|
61
|
+
super().__init__(autocommit)
|
|
62
|
+
self.session_factory = session_factory
|
|
63
|
+
self.session = None
|
|
64
|
+
|
|
65
|
+
async def initialize(self) -> None:
|
|
66
|
+
self.session = self.session_factory()
|
|
67
|
+
|
|
68
|
+
async def dispose(self) -> None:
|
|
69
|
+
await self.session.close()
|
|
70
|
+
|
|
71
|
+
async def commit(self) -> None:
|
|
72
|
+
await self.session.commit()
|
|
73
|
+
|
|
74
|
+
async def rollback(self) -> None:
|
|
75
|
+
await self.session.rollback()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Usage with context manager:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
async with transaction:
|
|
82
|
+
user = await repository.get(user_id)
|
|
83
|
+
user.name = "New Name"
|
|
84
|
+
await repository.save(user)
|
|
85
|
+
# Automatically commits on success, rollbacks on exception
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### External Proxy Pattern
|
|
89
|
+
|
|
90
|
+
Access external service data with proxy interfaces:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from spakky.data.external.proxy import ProxyModel, IAsyncGenericProxy
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ExternalUser(ProxyModel[int]):
|
|
97
|
+
name: str
|
|
98
|
+
email: str
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class IExternalUserProxy(IAsyncGenericProxy[ExternalUser, int]):
|
|
102
|
+
pass
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API Reference
|
|
106
|
+
|
|
107
|
+
### Persistency
|
|
108
|
+
|
|
109
|
+
| Class | Description |
|
|
110
|
+
|-------|-------------|
|
|
111
|
+
| `IGenericRepository` | Sync generic repository interface |
|
|
112
|
+
| `IAsyncGenericRepository` | Async generic repository interface |
|
|
113
|
+
| `AbstractTransaction` | Sync transaction with context manager |
|
|
114
|
+
| `AbstractAsyncTransaction` | Async transaction with context manager |
|
|
115
|
+
| `EntityNotFoundError` | Raised when entity not found |
|
|
116
|
+
|
|
117
|
+
### External
|
|
118
|
+
|
|
119
|
+
| Class | Description |
|
|
120
|
+
|-------|-------------|
|
|
121
|
+
| `ProxyModel` | Base class for external service data models |
|
|
122
|
+
| `IGenericProxy` | Sync proxy interface |
|
|
123
|
+
| `IAsyncGenericProxy` | Async proxy interface |
|
|
124
|
+
|
|
125
|
+
### Errors
|
|
126
|
+
|
|
127
|
+
| Class | Description |
|
|
128
|
+
|-------|-------------|
|
|
129
|
+
| `AbstractSpakkyPersistencyError` | Base error for persistency operations |
|
|
130
|
+
| `AbstractSpakkyExternalError` | Base error for external service operations |
|
|
131
|
+
|
|
132
|
+
## Related Packages
|
|
133
|
+
|
|
134
|
+
| Package | Description |
|
|
135
|
+
|---------|-------------|
|
|
136
|
+
| `spakky-domain` | DDD building blocks (Entity, AggregateRoot, ValueObject) |
|
|
137
|
+
| `spakky-event` | Event publisher/consumer interfaces |
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT License
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Spakky Data
|
|
2
|
+
|
|
3
|
+
Data access layer abstractions for [Spakky Framework](https://github.com/E5presso/spakky-framework).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install spakky-data
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Repository Pattern**: Generic repository interfaces for aggregate persistence
|
|
14
|
+
- **Transaction Management**: Abstract transaction classes with autocommit support
|
|
15
|
+
- **External Proxy**: Proxy pattern for external service data access
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### Repository Pattern
|
|
20
|
+
|
|
21
|
+
Define repository interfaces for your domain aggregates:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from abc import abstractmethod
|
|
25
|
+
from uuid import UUID
|
|
26
|
+
|
|
27
|
+
from spakky.data.persistency.repository import IAsyncGenericRepository
|
|
28
|
+
from spakky.domain.models.aggregate_root import AbstractAggregateRoot
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class User(AbstractAggregateRoot[UUID]):
|
|
32
|
+
name: str
|
|
33
|
+
email: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class IUserRepository(IAsyncGenericRepository[User, UUID]):
|
|
37
|
+
@abstractmethod
|
|
38
|
+
async def find_by_email(self, email: str) -> User | None: ...
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Transaction Management
|
|
42
|
+
|
|
43
|
+
Use abstract transactions for database operations:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from spakky.data.persistency.transaction import AbstractAsyncTransaction
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SQLAlchemyTransaction(AbstractAsyncTransaction):
|
|
50
|
+
def __init__(self, session_factory, autocommit: bool = True) -> None:
|
|
51
|
+
super().__init__(autocommit)
|
|
52
|
+
self.session_factory = session_factory
|
|
53
|
+
self.session = None
|
|
54
|
+
|
|
55
|
+
async def initialize(self) -> None:
|
|
56
|
+
self.session = self.session_factory()
|
|
57
|
+
|
|
58
|
+
async def dispose(self) -> None:
|
|
59
|
+
await self.session.close()
|
|
60
|
+
|
|
61
|
+
async def commit(self) -> None:
|
|
62
|
+
await self.session.commit()
|
|
63
|
+
|
|
64
|
+
async def rollback(self) -> None:
|
|
65
|
+
await self.session.rollback()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Usage with context manager:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
async with transaction:
|
|
72
|
+
user = await repository.get(user_id)
|
|
73
|
+
user.name = "New Name"
|
|
74
|
+
await repository.save(user)
|
|
75
|
+
# Automatically commits on success, rollbacks on exception
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### External Proxy Pattern
|
|
79
|
+
|
|
80
|
+
Access external service data with proxy interfaces:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from spakky.data.external.proxy import ProxyModel, IAsyncGenericProxy
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ExternalUser(ProxyModel[int]):
|
|
87
|
+
name: str
|
|
88
|
+
email: str
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class IExternalUserProxy(IAsyncGenericProxy[ExternalUser, int]):
|
|
92
|
+
pass
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## API Reference
|
|
96
|
+
|
|
97
|
+
### Persistency
|
|
98
|
+
|
|
99
|
+
| Class | Description |
|
|
100
|
+
|-------|-------------|
|
|
101
|
+
| `IGenericRepository` | Sync generic repository interface |
|
|
102
|
+
| `IAsyncGenericRepository` | Async generic repository interface |
|
|
103
|
+
| `AbstractTransaction` | Sync transaction with context manager |
|
|
104
|
+
| `AbstractAsyncTransaction` | Async transaction with context manager |
|
|
105
|
+
| `EntityNotFoundError` | Raised when entity not found |
|
|
106
|
+
|
|
107
|
+
### External
|
|
108
|
+
|
|
109
|
+
| Class | Description |
|
|
110
|
+
|-------|-------------|
|
|
111
|
+
| `ProxyModel` | Base class for external service data models |
|
|
112
|
+
| `IGenericProxy` | Sync proxy interface |
|
|
113
|
+
| `IAsyncGenericProxy` | Async proxy interface |
|
|
114
|
+
|
|
115
|
+
### Errors
|
|
116
|
+
|
|
117
|
+
| Class | Description |
|
|
118
|
+
|-------|-------------|
|
|
119
|
+
| `AbstractSpakkyPersistencyError` | Base error for persistency operations |
|
|
120
|
+
| `AbstractSpakkyExternalError` | Base error for external service operations |
|
|
121
|
+
|
|
122
|
+
## Related Packages
|
|
123
|
+
|
|
124
|
+
| Package | Description |
|
|
125
|
+
|---------|-------------|
|
|
126
|
+
| `spakky-domain` | DDD building blocks (Entity, AggregateRoot, ValueObject) |
|
|
127
|
+
| `spakky-event` | Event publisher/consumer interfaces |
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT License
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "spakky-data"
|
|
3
|
+
version = "4.0.0"
|
|
4
|
+
description = "Data access layer for Spakky Framework (Repository implementations, ORM integration)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "Spakky", email = "sejong418@icloud.com" }]
|
|
7
|
+
requires-python = ">=3.11"
|
|
8
|
+
dependencies = ["spakky-domain>=4.0.0"]
|
|
9
|
+
|
|
10
|
+
[build-system]
|
|
11
|
+
requires = ["uv_build>=0.9.5,<0.10.0"]
|
|
12
|
+
build-backend = "uv_build"
|
|
13
|
+
|
|
14
|
+
[tool.uv.build-backend]
|
|
15
|
+
module-root = "src"
|
|
16
|
+
module-name = "spakky.data"
|
|
17
|
+
|
|
18
|
+
[tool.pyrefly]
|
|
19
|
+
python-version = "3.14"
|
|
20
|
+
search_path = ["src", "."]
|
|
21
|
+
project_excludes = ["**/__pycache__", "**/*.pyc"]
|
|
22
|
+
|
|
23
|
+
[tool.ruff]
|
|
24
|
+
builtins = ["_"]
|
|
25
|
+
cache-dir = "~/.cache/ruff"
|
|
26
|
+
|
|
27
|
+
[tool.pytest.ini_options]
|
|
28
|
+
pythonpath = "src/spakky/data"
|
|
29
|
+
testpaths = "tests"
|
|
30
|
+
python_files = ["test_*.py"]
|
|
31
|
+
asyncio_mode = "auto"
|
|
32
|
+
addopts = """
|
|
33
|
+
--cov
|
|
34
|
+
--cov-report=term
|
|
35
|
+
--cov-report=xml
|
|
36
|
+
--no-cov-on-fail
|
|
37
|
+
--strict-markers
|
|
38
|
+
--dist=load
|
|
39
|
+
-p no:warnings
|
|
40
|
+
-n auto
|
|
41
|
+
-vv
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
[tool.coverage.run]
|
|
45
|
+
include = ["src/spakky/data/*"]
|
|
46
|
+
branch = true
|
|
47
|
+
|
|
48
|
+
[tool.coverage.report]
|
|
49
|
+
show_missing = true
|
|
50
|
+
precision = 2
|
|
51
|
+
fail_under = 90
|
|
52
|
+
skip_empty = true
|
|
53
|
+
exclude_lines = [
|
|
54
|
+
"pragma: no cover",
|
|
55
|
+
"def __repr__",
|
|
56
|
+
"raise AssertionError",
|
|
57
|
+
"raise NotImplementedError",
|
|
58
|
+
"@(abc\\.)?abstractmethod",
|
|
59
|
+
"@(typing\\.)?overload",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[tool.uv.sources]
|
|
63
|
+
spakky-domain = { workspace = true }
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Spakky Data package - Data access abstractions.
|
|
2
|
+
|
|
3
|
+
This package provides:
|
|
4
|
+
- Repository pattern for aggregate persistence
|
|
5
|
+
- Transaction management
|
|
6
|
+
- External service proxy pattern
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from spakky.data import IGenericRepository, AbstractTransaction
|
|
10
|
+
from spakky.data import IGenericProxy, IAsyncGenericProxy
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# Persistency
|
|
14
|
+
# External
|
|
15
|
+
from spakky.data.external.error import AbstractSpakkyExternalError
|
|
16
|
+
from spakky.data.external.proxy import IAsyncGenericProxy, IGenericProxy
|
|
17
|
+
from spakky.data.persistency.error import AbstractSpakkyPersistencyError
|
|
18
|
+
from spakky.data.persistency.repository import (
|
|
19
|
+
EntityNotFoundError,
|
|
20
|
+
IGenericRepository,
|
|
21
|
+
)
|
|
22
|
+
from spakky.data.persistency.transaction import (
|
|
23
|
+
AbstractAsyncTransaction,
|
|
24
|
+
AbstractTransaction,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
# Repository
|
|
29
|
+
"EntityNotFoundError",
|
|
30
|
+
"IGenericRepository",
|
|
31
|
+
# Transaction
|
|
32
|
+
"AbstractAsyncTransaction",
|
|
33
|
+
"AbstractTransaction",
|
|
34
|
+
# Proxy
|
|
35
|
+
"IAsyncGenericProxy",
|
|
36
|
+
"IGenericProxy",
|
|
37
|
+
# Errors
|
|
38
|
+
"AbstractSpakkyExternalError",
|
|
39
|
+
"AbstractSpakkyPersistencyError",
|
|
40
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""External service abstractions.
|
|
2
|
+
|
|
3
|
+
This module provides:
|
|
4
|
+
- Proxy pattern for external services
|
|
5
|
+
- External service errors
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from spakky.data.external.error import AbstractSpakkyExternalError
|
|
9
|
+
from spakky.data.external.proxy import IAsyncGenericProxy, IGenericProxy
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
# Proxy
|
|
13
|
+
"IAsyncGenericProxy",
|
|
14
|
+
"IGenericProxy",
|
|
15
|
+
# Errors
|
|
16
|
+
"AbstractSpakkyExternalError",
|
|
17
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Generic, Sequence, TypeVar
|
|
3
|
+
|
|
4
|
+
from spakky.core.common.interfaces.equatable import IEquatable
|
|
5
|
+
from spakky.core.common.mutability import immutable
|
|
6
|
+
|
|
7
|
+
ProxyIdT_contra = TypeVar("ProxyIdT_contra", bound=IEquatable, contravariant=True)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@immutable
|
|
11
|
+
class ProxyModel(IEquatable, Generic[ProxyIdT_contra]):
|
|
12
|
+
id: ProxyIdT_contra
|
|
13
|
+
|
|
14
|
+
def __eq__(self, other: object) -> bool:
|
|
15
|
+
if not isinstance(other, type(self)):
|
|
16
|
+
return False
|
|
17
|
+
return self.id == other.id
|
|
18
|
+
|
|
19
|
+
def __hash__(self) -> int:
|
|
20
|
+
return hash(self.id)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
ProxyModelT_co = TypeVar("ProxyModelT_co", bound=ProxyModel[Any], covariant=True)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class IGenericProxy(ABC, Generic[ProxyModelT_co, ProxyIdT_contra]):
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def get(self, proxy_id: ProxyIdT_contra) -> ProxyModelT_co: ...
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def get_or_none(self, proxy_id: ProxyIdT_contra) -> ProxyModelT_co | None: ...
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def contains(self, proxy_id: ProxyIdT_contra) -> bool: ...
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def range(
|
|
38
|
+
self, proxy_ids: Sequence[ProxyIdT_contra]
|
|
39
|
+
) -> Sequence[ProxyModelT_co]: ...
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class IAsyncGenericProxy(ABC, Generic[ProxyModelT_co, ProxyIdT_contra]):
|
|
43
|
+
@abstractmethod
|
|
44
|
+
async def get(self, proxy_id: ProxyIdT_contra) -> ProxyModelT_co: ...
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
async def get_or_none(self, proxy_id: ProxyIdT_contra) -> ProxyModelT_co | None: ...
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
async def contains(self, proxy_id: ProxyIdT_contra) -> bool: ...
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
async def range(
|
|
54
|
+
self, proxy_ids: Sequence[ProxyIdT_contra]
|
|
55
|
+
) -> Sequence[ProxyModelT_co]: ...
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Data persistence abstractions.
|
|
2
|
+
|
|
3
|
+
This module provides:
|
|
4
|
+
- Repository pattern interfaces
|
|
5
|
+
- Transaction management
|
|
6
|
+
- Persistence-related errors
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from spakky.data.persistency.error import AbstractSpakkyPersistencyError
|
|
10
|
+
from spakky.data.persistency.repository import (
|
|
11
|
+
EntityNotFoundError,
|
|
12
|
+
IGenericRepository,
|
|
13
|
+
)
|
|
14
|
+
from spakky.data.persistency.transaction import (
|
|
15
|
+
AbstractAsyncTransaction,
|
|
16
|
+
AbstractTransaction,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
# Repository
|
|
21
|
+
"EntityNotFoundError",
|
|
22
|
+
"IGenericRepository",
|
|
23
|
+
# Transaction
|
|
24
|
+
"AbstractAsyncTransaction",
|
|
25
|
+
"AbstractTransaction",
|
|
26
|
+
# Errors
|
|
27
|
+
"AbstractSpakkyPersistencyError",
|
|
28
|
+
]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Generic, Sequence, TypeVar
|
|
3
|
+
|
|
4
|
+
from spakky.core.common.interfaces.equatable import IEquatable
|
|
5
|
+
|
|
6
|
+
from spakky.domain.error import AbstractSpakkyDomainError
|
|
7
|
+
from spakky.domain.models.aggregate_root import AggregateRootT
|
|
8
|
+
|
|
9
|
+
AggregateIdT_contra = TypeVar(
|
|
10
|
+
"AggregateIdT_contra", bound=IEquatable, contravariant=True
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EntityNotFoundError(AbstractSpakkyDomainError):
|
|
15
|
+
message = "Entity not found by given id"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class IGenericRepository(ABC, Generic[AggregateRootT, AggregateIdT_contra]):
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def get(self, aggregate_id: AggregateIdT_contra) -> AggregateRootT: ...
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def get_or_none(
|
|
24
|
+
self, aggregate_id: AggregateIdT_contra
|
|
25
|
+
) -> AggregateRootT | None: ...
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def contains(self, aggregate_id: AggregateIdT_contra) -> bool: ...
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def range(
|
|
32
|
+
self, aggregate_ids: Sequence[AggregateIdT_contra]
|
|
33
|
+
) -> Sequence[AggregateRootT]: ...
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def save(self, aggregate: AggregateRootT) -> AggregateRootT: ...
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def save_all(
|
|
40
|
+
self, aggregates: Sequence[AggregateRootT]
|
|
41
|
+
) -> Sequence[AggregateRootT]: ...
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def delete(self, aggregate: AggregateRootT) -> AggregateRootT: ...
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def delete_all(
|
|
48
|
+
self, aggregates: Sequence[AggregateRootT]
|
|
49
|
+
) -> Sequence[AggregateRootT]: ...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class IAsyncGenericRepository(ABC, Generic[AggregateRootT, AggregateIdT_contra]):
|
|
53
|
+
@abstractmethod
|
|
54
|
+
async def get(self, aggregate_id: AggregateIdT_contra) -> AggregateRootT: ...
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
async def get_or_none(
|
|
58
|
+
self, aggregate_id: AggregateIdT_contra
|
|
59
|
+
) -> AggregateRootT | None: ...
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
async def contains(self, aggregate_id: AggregateIdT_contra) -> bool: ...
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
async def range(
|
|
66
|
+
self, aggregate_ids: Sequence[AggregateIdT_contra]
|
|
67
|
+
) -> Sequence[AggregateRootT]: ...
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
async def save(self, aggregate: AggregateRootT) -> AggregateRootT: ...
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
async def save_all(
|
|
74
|
+
self, aggregates: Sequence[AggregateRootT]
|
|
75
|
+
) -> Sequence[AggregateRootT]: ...
|
|
76
|
+
|
|
77
|
+
@abstractmethod
|
|
78
|
+
async def delete(self, aggregate: AggregateRootT) -> AggregateRootT: ...
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
async def delete_all(
|
|
82
|
+
self, aggregates: Sequence[AggregateRootT]
|
|
83
|
+
) -> Sequence[AggregateRootT]: ...
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from types import TracebackType
|
|
3
|
+
from typing import Self, final
|
|
4
|
+
|
|
5
|
+
from spakky.core.common.interfaces.disposable import IAsyncDisposable, IDisposable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AbstractTransaction(IDisposable, ABC):
|
|
9
|
+
autocommit_enabled: bool
|
|
10
|
+
|
|
11
|
+
def __init__(self, autocommit: bool = True) -> None:
|
|
12
|
+
self.autocommit_enabled = autocommit
|
|
13
|
+
|
|
14
|
+
@final
|
|
15
|
+
def __enter__(self) -> Self:
|
|
16
|
+
self.initialize()
|
|
17
|
+
return self
|
|
18
|
+
|
|
19
|
+
@final
|
|
20
|
+
def __exit__(
|
|
21
|
+
self,
|
|
22
|
+
__exc_type: type[BaseException] | None,
|
|
23
|
+
__exc_value: BaseException | None,
|
|
24
|
+
__traceback: TracebackType | None,
|
|
25
|
+
) -> bool | None:
|
|
26
|
+
if __exc_value is not None:
|
|
27
|
+
self.rollback()
|
|
28
|
+
self.dispose()
|
|
29
|
+
return
|
|
30
|
+
try:
|
|
31
|
+
if self.autocommit_enabled:
|
|
32
|
+
self.commit()
|
|
33
|
+
except:
|
|
34
|
+
self.rollback()
|
|
35
|
+
raise
|
|
36
|
+
finally:
|
|
37
|
+
self.dispose()
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def initialize(self) -> None: ...
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def dispose(self) -> None: ...
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def commit(self) -> None: ...
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def rollback(self) -> None: ...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class AbstractAsyncTransaction(IAsyncDisposable, ABC):
|
|
53
|
+
autocommit_enabled: bool
|
|
54
|
+
|
|
55
|
+
def __init__(self, autocommit: bool = True) -> None:
|
|
56
|
+
self.autocommit_enabled = autocommit
|
|
57
|
+
|
|
58
|
+
@final
|
|
59
|
+
async def __aenter__(self) -> Self:
|
|
60
|
+
await self.initialize()
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
@final
|
|
64
|
+
async def __aexit__(
|
|
65
|
+
self,
|
|
66
|
+
__exc_type: type[BaseException] | None,
|
|
67
|
+
__exc_value: BaseException | None,
|
|
68
|
+
__traceback: TracebackType | None,
|
|
69
|
+
) -> bool | None:
|
|
70
|
+
if __exc_value is not None:
|
|
71
|
+
await self.rollback()
|
|
72
|
+
await self.dispose()
|
|
73
|
+
return
|
|
74
|
+
try:
|
|
75
|
+
if self.autocommit_enabled:
|
|
76
|
+
await self.commit()
|
|
77
|
+
except:
|
|
78
|
+
await self.rollback()
|
|
79
|
+
raise
|
|
80
|
+
finally:
|
|
81
|
+
await self.dispose()
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
async def initialize(self) -> None: ...
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
async def dispose(self) -> None: ...
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
async def commit(self) -> None: ...
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
async def rollback(self) -> None: ...
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Repository stereotype for data access layer.
|
|
2
|
+
|
|
3
|
+
This module provides @Repository stereotype for organizing classes
|
|
4
|
+
that handle data persistence and retrieval.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from spakky.core.pod.annotations.pod import Pod
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(eq=False)
|
|
13
|
+
class Repository(Pod):
|
|
14
|
+
"""Stereotype for repository classes handling data access.
|
|
15
|
+
|
|
16
|
+
Repositories provide an abstraction over data sources,
|
|
17
|
+
implementing data access patterns and queries.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
...
|