SARepo 0.1.0__tar.gz → 0.1.3__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.
- {sarepo-0.1.0 → sarepo-0.1.3}/PKG-INFO +1 -1
- {sarepo-0.1.0 → sarepo-0.1.3}/SARepo/repo.py +3 -1
- {sarepo-0.1.0 → sarepo-0.1.3}/SARepo/sa_repo.py +46 -2
- {sarepo-0.1.0 → sarepo-0.1.3}/SARepo.egg-info/PKG-INFO +1 -1
- {sarepo-0.1.0 → sarepo-0.1.3}/pyproject.toml +1 -1
- sarepo-0.1.3/tests/test_sync_basic.py +73 -0
- sarepo-0.1.0/tests/test_sync_basic.py +0 -30
- {sarepo-0.1.0 → sarepo-0.1.3}/LICENSE +0 -0
- {sarepo-0.1.0 → sarepo-0.1.3}/README.md +0 -0
- {sarepo-0.1.0 → sarepo-0.1.3}/SARepo/__init__.py +0 -0
- {sarepo-0.1.0 → sarepo-0.1.3}/SARepo/base.py +0 -0
- {sarepo-0.1.0 → sarepo-0.1.3}/SARepo/models.py +0 -0
- {sarepo-0.1.0 → sarepo-0.1.3}/SARepo/specs.py +0 -0
- {sarepo-0.1.0 → sarepo-0.1.3}/SARepo/uow.py +0 -0
- {sarepo-0.1.0 → sarepo-0.1.3}/SARepo.egg-info/SOURCES.txt +0 -0
- {sarepo-0.1.0 → sarepo-0.1.3}/SARepo.egg-info/dependency_links.txt +0 -0
- {sarepo-0.1.0 → sarepo-0.1.3}/SARepo.egg-info/requires.txt +0 -0
- {sarepo-0.1.0 → sarepo-0.1.3}/SARepo.egg-info/top_level.txt +0 -0
- {sarepo-0.1.0 → sarepo-0.1.3}/setup.cfg +0 -0
@@ -1,14 +1,16 @@
|
|
1
1
|
|
2
|
-
from typing import Generic, TypeVar, Type, Optional, Any, Protocol
|
2
|
+
from typing import Generic, List, TypeVar, Type, Optional, Any, Protocol
|
3
3
|
from .base import Page, PageRequest
|
4
4
|
|
5
5
|
T = TypeVar("T")
|
6
6
|
|
7
7
|
class CrudRepository(Protocol, Generic[T]):
|
8
8
|
model: Type[T]
|
9
|
+
def getAll(self, limit: Optional[int]) -> List[T]: ...
|
9
10
|
def get(self, id_: Any) -> T: ...
|
10
11
|
def try_get(self, id_: Any) -> Optional[T]: ...
|
11
12
|
def add(self, entity: T) -> T: ...
|
12
13
|
def update(self, entity: T) -> T: ...
|
13
14
|
def remove(self, entity: T) -> None: ...
|
15
|
+
def delete_by_id(self, id_: Any) -> bool: ...
|
14
16
|
def page(self, page: PageRequest, spec=None, order_by=None) -> Page[T]: ...
|
@@ -1,8 +1,8 @@
|
|
1
1
|
|
2
|
-
from typing import Type, Generic, TypeVar, Optional, Sequence, Any, Callable
|
2
|
+
from typing import List, Type, Generic, TypeVar, Optional, Sequence, Any, Callable
|
3
3
|
from sqlalchemy.orm import Session
|
4
4
|
from sqlalchemy.ext.asyncio import AsyncSession
|
5
|
-
from sqlalchemy import select, func
|
5
|
+
from sqlalchemy import inspect, select, func
|
6
6
|
from .base import PageRequest, Page, NotFoundError
|
7
7
|
|
8
8
|
T = TypeVar("T")
|
@@ -17,6 +17,13 @@ class SARepository(Generic[T]):
|
|
17
17
|
def _select(self):
|
18
18
|
return select(self.model)
|
19
19
|
|
20
|
+
def getAll(self, limit: Optional[int] = None) -> List[T]:
|
21
|
+
stmt = select(self.model)
|
22
|
+
if limit is not None:
|
23
|
+
stmt = stmt.limit(limit)
|
24
|
+
result = self.session.execute(stmt)
|
25
|
+
return result.scalars().all()
|
26
|
+
|
20
27
|
def get(self, id_: Any) -> T:
|
21
28
|
obj = self.session.get(self.model, id_)
|
22
29
|
if not obj:
|
@@ -38,11 +45,26 @@ class SARepository(Generic[T]):
|
|
38
45
|
return entity
|
39
46
|
|
40
47
|
def remove(self, entity: T) -> None:
|
48
|
+
insp = inspect(entity, raiseerr=False)
|
49
|
+
if not (insp and (insp.persistent or insp.pending)):
|
50
|
+
pk = getattr(entity, "id", None)
|
51
|
+
if pk is None:
|
52
|
+
raise ValueError("remove() needs a persistent entity or an entity with a primary key set")
|
53
|
+
entity = self.session.get(self.model, pk)
|
54
|
+
if entity is None:
|
55
|
+
return
|
41
56
|
if hasattr(entity, "is_deleted"):
|
42
57
|
setattr(entity, "is_deleted", True)
|
43
58
|
else:
|
44
59
|
self.session.delete(entity)
|
45
60
|
|
61
|
+
def delete_by_id(self, id_: Any) -> bool:
|
62
|
+
obj = self.session.get(self.model, id_)
|
63
|
+
if not obj:
|
64
|
+
return False
|
65
|
+
self.remove(obj)
|
66
|
+
return True
|
67
|
+
|
46
68
|
def page(self, page: PageRequest, spec: Optional[Spec] = None, order_by=None) -> Page[T]:
|
47
69
|
stmt = self._select()
|
48
70
|
if spec:
|
@@ -62,6 +84,13 @@ class SAAsyncRepository(Generic[T]):
|
|
62
84
|
def __init__(self, model: Type[T], session: AsyncSession):
|
63
85
|
self.model = model
|
64
86
|
self.session = session
|
87
|
+
|
88
|
+
async def getAll(self, limit: Optional[int] = None) -> List[T]:
|
89
|
+
stmt = select(self.model)
|
90
|
+
if limit is not None:
|
91
|
+
stmt = stmt.limit(limit)
|
92
|
+
result = await self.session.execute(stmt)
|
93
|
+
return result.scalars().all()
|
65
94
|
|
66
95
|
def _select(self):
|
67
96
|
return select(self.model)
|
@@ -87,11 +116,26 @@ class SAAsyncRepository(Generic[T]):
|
|
87
116
|
return entity
|
88
117
|
|
89
118
|
async def remove(self, entity: T) -> None:
|
119
|
+
insp = inspect(entity, raiseerr=False)
|
120
|
+
if not (insp and (insp.persistent or insp.pending)):
|
121
|
+
pk = getattr(entity, "id", None)
|
122
|
+
if pk is None:
|
123
|
+
raise ValueError("remove() needs a persistent entity or an entity with a primary key set")
|
124
|
+
entity = await self.session.get(self.model, pk)
|
125
|
+
if entity is None:
|
126
|
+
return
|
90
127
|
if hasattr(entity, "is_deleted"):
|
91
128
|
setattr(entity, "is_deleted", True)
|
92
129
|
else:
|
93
130
|
await self.session.delete(entity)
|
94
131
|
|
132
|
+
async def delete_by_id(self, id_: Any) -> bool:
|
133
|
+
obj = await self.session.get(self.model, id_)
|
134
|
+
if not obj:
|
135
|
+
return False
|
136
|
+
await self.remove(obj)
|
137
|
+
return True
|
138
|
+
|
95
139
|
async def page(self, page: PageRequest, spec: Optional[Spec] = None, order_by=None) -> Page[T]:
|
96
140
|
stmt = self._select()
|
97
141
|
if spec:
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import pytest
|
2
|
+
from sqlalchemy import create_engine, String
|
3
|
+
from sqlalchemy.orm import sessionmaker, DeclarativeBase, Mapped, mapped_column
|
4
|
+
from SARepo.sa_repo import SARepository
|
5
|
+
from SARepo.base import PageRequest, NotFoundError
|
6
|
+
|
7
|
+
class Base(DeclarativeBase): pass
|
8
|
+
|
9
|
+
class Item(Base):
|
10
|
+
__tablename__ = "items"
|
11
|
+
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
12
|
+
title: Mapped[str] = mapped_column(String(100), nullable=False)
|
13
|
+
|
14
|
+
def test_crud_and_pagination():
|
15
|
+
engine = create_engine("sqlite+pysqlite:///:memory:", echo=False)
|
16
|
+
Base.metadata.create_all(engine)
|
17
|
+
SessionLocal = sessionmaker(bind=engine, expire_on_commit=False)
|
18
|
+
|
19
|
+
with SessionLocal() as session:
|
20
|
+
repo = SARepository(Item, session)
|
21
|
+
for i in range(25):
|
22
|
+
repo.add(Item(title=f"t{i}"))
|
23
|
+
session.commit()
|
24
|
+
|
25
|
+
page = repo.page(PageRequest(1, 10))
|
26
|
+
assert page.total == 25
|
27
|
+
assert len(page.items) == 10
|
28
|
+
assert page.page == 1
|
29
|
+
assert page.pages == 3
|
30
|
+
# проверим, что конкретные объекты есть
|
31
|
+
got = {it.title for it in page.items}
|
32
|
+
assert got.issubset({f"t{i}" for i in range(25)})
|
33
|
+
|
34
|
+
def test_get_all():
|
35
|
+
engine = create_engine("sqlite+pysqlite:///:memory:", echo=False)
|
36
|
+
Base.metadata.create_all(engine)
|
37
|
+
SessionLocal = sessionmaker(bind=engine, expire_on_commit=False)
|
38
|
+
|
39
|
+
with SessionLocal() as session:
|
40
|
+
repo = SARepository(Item, session)
|
41
|
+
for i in range(10):
|
42
|
+
repo.add(Item(title=f"get-all-{i}"))
|
43
|
+
session.commit()
|
44
|
+
|
45
|
+
items = repo.getAll()
|
46
|
+
assert isinstance(items, list)
|
47
|
+
assert len(items) == 10
|
48
|
+
titles = {i.title for i in items}
|
49
|
+
assert titles == {f"get-all-{i}" for i in range(10)}
|
50
|
+
|
51
|
+
items2 = repo.getAll(5)
|
52
|
+
assert isinstance(items2, list)
|
53
|
+
assert len(items2) == 5
|
54
|
+
titles = {i.title for i in items2}
|
55
|
+
assert titles == {f"get-all-{i}" for i in range(5)}
|
56
|
+
|
57
|
+
def test_get_and_try_get():
|
58
|
+
engine = create_engine("sqlite+pysqlite:///:memory:", echo=False)
|
59
|
+
Base.metadata.create_all(engine)
|
60
|
+
SessionLocal = sessionmaker(bind=engine, expire_on_commit=False)
|
61
|
+
|
62
|
+
with SessionLocal() as session:
|
63
|
+
repo = SARepository(Item, session)
|
64
|
+
obj = repo.add(Item(title="one"))
|
65
|
+
session.commit()
|
66
|
+
|
67
|
+
same = repo.get(obj.id)
|
68
|
+
assert same.id == obj.id and same.title == "one"
|
69
|
+
|
70
|
+
assert repo.try_get(9999) is None
|
71
|
+
|
72
|
+
with pytest.raises(NotFoundError):
|
73
|
+
repo.get(9999)
|
@@ -1,30 +0,0 @@
|
|
1
|
-
|
2
|
-
import pytest
|
3
|
-
from sqlalchemy import create_engine, String
|
4
|
-
from sqlalchemy.orm import sessionmaker, DeclarativeBase, Mapped, mapped_column
|
5
|
-
from SARepoo.sa_repo import SARepository
|
6
|
-
from SARepoo.base import PageRequest
|
7
|
-
|
8
|
-
class Base(DeclarativeBase): pass
|
9
|
-
|
10
|
-
class Item(Base):
|
11
|
-
__tablename__ = "items"
|
12
|
-
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
13
|
-
title: Mapped[str] = mapped_column(String(100))
|
14
|
-
|
15
|
-
def test_crud_and_pagination():
|
16
|
-
engine = create_engine("sqlite+pysqlite:///:memory:", echo=False)
|
17
|
-
Base.metadata.create_all(engine)
|
18
|
-
SessionLocal = sessionmaker(bind=engine, expire_on_commit=False)
|
19
|
-
|
20
|
-
with SessionLocal() as session:
|
21
|
-
repo = SARepository(Item, session)
|
22
|
-
for i in range(25):
|
23
|
-
repo.add(Item(title=f"t{i}"))
|
24
|
-
session.commit()
|
25
|
-
|
26
|
-
page = repo.page(PageRequest(1, 10))
|
27
|
-
assert page.total == 25
|
28
|
-
assert len(page.items) == 10
|
29
|
-
assert page.page == 1
|
30
|
-
assert page.pages == 3
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|