SARepo 0.1.6__py3-none-any.whl → 0.1.7__py3-none-any.whl
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/repo.py +52 -28
- SARepo/sa_repo.py +121 -70
- {sarepo-0.1.6.dist-info → sarepo-0.1.7.dist-info}/METADATA +1 -1
- sarepo-0.1.7.dist-info/RECORD +12 -0
- sarepo-0.1.6.dist-info/RECORD +0 -12
- {sarepo-0.1.6.dist-info → sarepo-0.1.7.dist-info}/WHEEL +0 -0
- {sarepo-0.1.6.dist-info → sarepo-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {sarepo-0.1.6.dist-info → sarepo-0.1.7.dist-info}/top_level.txt +0 -0
SARepo/repo.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
from typing import Generic, List, TypeVar, Type, Optional, Any, Protocol
|
3
2
|
|
4
3
|
from SARepo.sa_repo import Spec
|
@@ -6,45 +5,70 @@ from .base import Page, PageRequest
|
|
6
5
|
|
7
6
|
T = TypeVar("T")
|
8
7
|
|
8
|
+
|
9
9
|
class CrudRepository(Protocol, Generic[T]):
|
10
10
|
model: Type[T]
|
11
|
-
|
12
|
-
def
|
13
|
-
|
11
|
+
|
12
|
+
def getAll(self,
|
13
|
+
limit: Optional[int] = None,
|
14
|
+
*,
|
15
|
+
include_deleted: bool = False,
|
16
|
+
order_by=None,
|
17
|
+
**filters) -> List[T]: ...
|
18
|
+
|
19
|
+
def get(self, id_: Any = None, *, include_deleted: bool = False, **filters) -> T: ...
|
20
|
+
|
21
|
+
def try_get(self, id_: Any = None, *, include_deleted: bool = False, **filters) -> Optional[T]: ...
|
22
|
+
|
14
23
|
def add(self, entity: T) -> T: ...
|
24
|
+
|
15
25
|
def update(self, entity: T) -> T: ...
|
26
|
+
|
16
27
|
def remove(self, entity: Optional[T] = None, id: Optional[Any] = None) -> bool: ...
|
28
|
+
|
17
29
|
def delete_by_id(self, id_: Any) -> bool: ...
|
18
|
-
|
30
|
+
|
31
|
+
def page(self, page: PageRequest, spec: Optional[Spec] = None, order_by=None, *, include_deleted: bool = False) -> \
|
32
|
+
Page[T]: ...
|
33
|
+
|
19
34
|
def get_all_by_column(
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
) -> list[T]: ...
|
35
|
+
self,
|
36
|
+
column_name: str,
|
37
|
+
value: Any,
|
38
|
+
*,
|
39
|
+
limit: Optional[int] = None,
|
40
|
+
order_by=None,
|
41
|
+
include_deleted: bool = False,
|
42
|
+
**extra_filters
|
43
|
+
) -> list[T]: ...
|
44
|
+
|
29
45
|
def find_all_by_column(
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
) -> list[T]: ...
|
46
|
+
self,
|
47
|
+
column_name: str,
|
48
|
+
value: Any,
|
49
|
+
*,
|
50
|
+
limit: Optional[int] = None,
|
51
|
+
order_by=None,
|
52
|
+
include_deleted: bool = False,
|
53
|
+
**extra_filters
|
54
|
+
) -> list[T]: ...
|
55
|
+
|
39
56
|
def get_or_create(
|
40
|
-
|
41
|
-
|
42
|
-
|
57
|
+
self,
|
58
|
+
defaults: Optional[dict] = None,
|
59
|
+
**unique_filters
|
43
60
|
) -> tuple[T, bool]: ...
|
61
|
+
|
44
62
|
def raw_query(self, sql: str, params: Optional[dict] = None) -> list[dict]: ...
|
63
|
+
|
45
64
|
def aggregate_avg(self, column_name: str, **filters) -> Optional[float]: ...
|
65
|
+
|
46
66
|
def aggregate_min(self, column_name: str, **filters): ...
|
67
|
+
|
47
68
|
def aggregate_max(self, column_name: str, **filters): ...
|
48
|
-
|
69
|
+
|
70
|
+
def aggregate_sum(self, column_name: str, **filters): ...
|
71
|
+
|
49
72
|
def count(self, **filters) -> int: ...
|
50
|
-
|
73
|
+
|
74
|
+
def restore(self, id_: Any) -> bool: ...
|
SARepo/sa_repo.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
from typing import List, Type, Generic, TypeVar, Optional, Sequence, Any, Callable
|
3
2
|
from sqlalchemy.orm import Session
|
4
3
|
from sqlalchemy.ext.asyncio import AsyncSession
|
@@ -8,8 +7,10 @@ from .base import PageRequest, Page, NotFoundError
|
|
8
7
|
T = TypeVar("T")
|
9
8
|
Spec = Callable
|
10
9
|
|
10
|
+
|
11
11
|
class SARepository(Generic[T]):
|
12
12
|
"""Synchronous repository implementation for SQLAlchemy 2.x."""
|
13
|
+
|
13
14
|
def __init__(self, model: Type[T], session: Session):
|
14
15
|
self.model = model
|
15
16
|
self.session = session
|
@@ -31,35 +32,52 @@ class SARepository(Generic[T]):
|
|
31
32
|
def _has_soft_delete(self) -> bool:
|
32
33
|
return hasattr(self.model, "is_deleted")
|
33
34
|
|
34
|
-
def _apply_alive_filter(self, stmt, include_deleted: bool)
|
35
|
+
def _apply_alive_filter(self, stmt, include_deleted: bool):
|
35
36
|
if self._has_soft_delete() and not include_deleted:
|
36
37
|
stmt = stmt.where(self.model.is_deleted == False) # noqa: E712
|
37
38
|
return stmt
|
38
|
-
|
39
|
-
def getAll(
|
39
|
+
|
40
|
+
def getAll(
|
41
|
+
self,
|
42
|
+
limit: Optional[int] = None,
|
43
|
+
*,
|
44
|
+
include_deleted: bool = False,
|
45
|
+
order_by=None,
|
46
|
+
**filters
|
47
|
+
) -> List[T]:
|
40
48
|
stmt = select(self.model)
|
49
|
+
stmt = self._apply_filters(stmt, **filters)
|
41
50
|
stmt = self._apply_alive_filter(stmt, include_deleted)
|
51
|
+
if order_by is not None:
|
52
|
+
stmt = stmt.order_by(order_by)
|
42
53
|
if limit is not None:
|
43
54
|
stmt = stmt.limit(limit)
|
44
55
|
result = self.session.execute(stmt)
|
45
|
-
return result.scalars().all()
|
46
|
-
|
47
|
-
def get(self, id_: Any, *, include_deleted: bool = False) -> T:
|
48
|
-
|
49
|
-
|
50
|
-
|
56
|
+
return result.scalars().all()
|
57
|
+
|
58
|
+
def get(self, id_: Any = None, *, include_deleted: bool = False, **filters) -> T:
|
59
|
+
if id_ is not None:
|
60
|
+
obj = self.session.get(self.model, id_)
|
61
|
+
if not obj:
|
62
|
+
raise NotFoundError(f"{self.model.__name__}({id_}) not found")
|
63
|
+
else:
|
64
|
+
stmt = select(self.model)
|
65
|
+
stmt = self._apply_filters(stmt, **filters)
|
66
|
+
stmt = self._apply_alive_filter(stmt, include_deleted)
|
67
|
+
res = self.session.execute(stmt)
|
68
|
+
obj = res.scalars().first()
|
69
|
+
if not obj:
|
70
|
+
raise NotFoundError(f"{self.model.__name__} not found for {filters}")
|
51
71
|
if self._has_soft_delete() and not include_deleted and getattr(obj, "is_deleted", False):
|
52
|
-
raise NotFoundError(f"{self.model.__name__}({
|
72
|
+
raise NotFoundError(f"{self.model.__name__}({getattr(obj, 'id', '?')}) deleted")
|
53
73
|
return obj
|
54
74
|
|
55
|
-
def try_get(self, id_: Any, *, include_deleted: bool = False) -> Optional[T]:
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
if self._has_soft_delete() and not include_deleted and getattr(obj, "is_deleted", False):
|
75
|
+
def try_get(self, id_: Any = None, *, include_deleted: bool = False, **filters) -> Optional[T]:
|
76
|
+
try:
|
77
|
+
return self.get(id_=id_, include_deleted=include_deleted, **filters)
|
78
|
+
except NotFoundError:
|
60
79
|
return None
|
61
|
-
|
62
|
-
|
80
|
+
|
63
81
|
def add(self, entity: T) -> T:
|
64
82
|
self.session.add(entity)
|
65
83
|
self.session.flush()
|
@@ -74,10 +92,10 @@ class SARepository(Generic[T]):
|
|
74
92
|
def remove(self, entity: Optional[T] = None, id: Optional[Any] = None) -> bool:
|
75
93
|
if entity is None and id is None:
|
76
94
|
raise ValueError("remove() requires either entity or id")
|
77
|
-
|
95
|
+
|
78
96
|
if id is not None:
|
79
97
|
return self._delete_by_id(id)
|
80
|
-
|
98
|
+
|
81
99
|
insp = inspect(entity, raiseerr=False)
|
82
100
|
if not (insp and (insp.persistent or insp.pending)):
|
83
101
|
pk = getattr(entity, "id", None)
|
@@ -90,7 +108,7 @@ class SARepository(Generic[T]):
|
|
90
108
|
setattr(entity, "is_deleted", True)
|
91
109
|
else:
|
92
110
|
self.session.delete(entity)
|
93
|
-
|
111
|
+
|
94
112
|
return True
|
95
113
|
|
96
114
|
def _delete_by_id(self, id_: Any) -> bool:
|
@@ -100,7 +118,8 @@ class SARepository(Generic[T]):
|
|
100
118
|
self.remove(obj)
|
101
119
|
return True
|
102
120
|
|
103
|
-
def page(self, page: PageRequest, spec: Optional[Spec] = None, order_by=None, *, include_deleted: bool = False) ->
|
121
|
+
def page(self, page: PageRequest, spec: Optional[Spec] = None, order_by=None, *, include_deleted: bool = False) -> \
|
122
|
+
Page[T]:
|
104
123
|
base = self._select()
|
105
124
|
if spec:
|
106
125
|
base = spec(base)
|
@@ -116,16 +135,16 @@ class SARepository(Generic[T]):
|
|
116
135
|
base.offset(page.page * page.size).limit(page.size)
|
117
136
|
).scalars().all()
|
118
137
|
return Page(items, total, page.page, page.size)
|
119
|
-
|
138
|
+
|
120
139
|
def get_all_by_column(
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
140
|
+
self,
|
141
|
+
column_name: str,
|
142
|
+
value: Any,
|
143
|
+
*,
|
144
|
+
limit: Optional[int] = None,
|
145
|
+
order_by=None,
|
146
|
+
include_deleted: bool = False,
|
147
|
+
**extra_filters
|
129
148
|
) -> list[T]:
|
130
149
|
col = self._resolve_column(column_name)
|
131
150
|
stmt = select(self.model).where(col == value)
|
@@ -137,15 +156,15 @@ class SARepository(Generic[T]):
|
|
137
156
|
stmt = stmt.limit(limit)
|
138
157
|
res = self.session.execute(stmt)
|
139
158
|
return res.scalars().all()
|
140
|
-
|
159
|
+
|
141
160
|
# Alias
|
142
161
|
def find_all_by_column(self, *args, **kwargs):
|
143
162
|
return self.get_all_by_column(*args, **kwargs)
|
144
|
-
|
163
|
+
|
145
164
|
def get_or_create(
|
146
|
-
|
147
|
-
|
148
|
-
|
165
|
+
self,
|
166
|
+
defaults: Optional[dict] = None,
|
167
|
+
**unique_filters
|
149
168
|
) -> tuple[T, bool]:
|
150
169
|
"""
|
151
170
|
Возвращает (obj, created). unique_filters определяют уникальность.
|
@@ -228,12 +247,14 @@ class SARepository(Generic[T]):
|
|
228
247
|
return True
|
229
248
|
return False
|
230
249
|
|
250
|
+
|
231
251
|
class SAAsyncRepository(Generic[T]):
|
232
252
|
"""Async repository implementation for SQLAlchemy 2.x."""
|
253
|
+
|
233
254
|
def __init__(self, model: Type[T], session: AsyncSession):
|
234
255
|
self.model = model
|
235
256
|
self.session = session
|
236
|
-
|
257
|
+
|
237
258
|
def _resolve_column(self, column_name: str):
|
238
259
|
try:
|
239
260
|
return getattr(self.model, column_name)
|
@@ -244,41 +265,71 @@ class SAAsyncRepository(Generic[T]):
|
|
244
265
|
if filters:
|
245
266
|
stmt = stmt.filter_by(**filters)
|
246
267
|
return stmt
|
247
|
-
|
268
|
+
|
248
269
|
def _select(self):
|
249
270
|
return select(self.model)
|
250
|
-
|
271
|
+
|
251
272
|
def _has_soft_delete(self) -> bool:
|
252
273
|
return hasattr(self.model, "is_deleted")
|
253
274
|
|
254
|
-
def _apply_alive_filter(self, stmt, include_deleted: bool)
|
275
|
+
def _apply_alive_filter(self, stmt, include_deleted: bool):
|
255
276
|
if self._has_soft_delete() and not include_deleted:
|
256
277
|
stmt = stmt.where(self.model.is_deleted == False) # noqa: E712
|
257
278
|
return stmt
|
258
279
|
|
259
|
-
async def getAll(
|
280
|
+
async def getAll(
|
281
|
+
self,
|
282
|
+
limit: Optional[int] = None,
|
283
|
+
*,
|
284
|
+
include_deleted: bool = False,
|
285
|
+
order_by=None,
|
286
|
+
**filters
|
287
|
+
) -> List[T]:
|
288
|
+
"""
|
289
|
+
Получить все записи с возможностью фильтрации (username='foo', age__gt=20, и т.д.)
|
290
|
+
"""
|
260
291
|
stmt = select(self.model)
|
292
|
+
stmt = self._apply_filters(stmt, **filters)
|
261
293
|
stmt = self._apply_alive_filter(stmt, include_deleted)
|
294
|
+
if order_by is not None:
|
295
|
+
stmt = stmt.order_by(order_by)
|
262
296
|
if limit is not None:
|
263
297
|
stmt = stmt.limit(limit)
|
264
298
|
result = await self.session.execute(stmt)
|
265
299
|
return result.scalars().all()
|
266
300
|
|
267
|
-
async def get(self, id_: Any, *, include_deleted: bool = False) -> T:
|
268
|
-
|
269
|
-
|
270
|
-
|
301
|
+
async def get(self, id_: Any = None, *, include_deleted: bool = False, **filters) -> T:
|
302
|
+
"""
|
303
|
+
Получить один объект по id или по произвольным фильтрам.
|
304
|
+
Пример:
|
305
|
+
await repo.get(id_=1)
|
306
|
+
await repo.get(username='ibrahim')
|
307
|
+
await repo.get(username__ilike='%rah%')
|
308
|
+
"""
|
309
|
+
if id_ is not None:
|
310
|
+
obj = await self.session.get(self.model, id_)
|
311
|
+
if not obj:
|
312
|
+
raise NotFoundError(f"{self.model.__name__}({id_}) not found")
|
313
|
+
else:
|
314
|
+
stmt = select(self.model)
|
315
|
+
stmt = self._apply_filters(stmt, **filters)
|
316
|
+
stmt = self._apply_alive_filter(stmt, include_deleted)
|
317
|
+
res = await self.session.execute(stmt)
|
318
|
+
obj = res.scalars().first()
|
319
|
+
if not obj:
|
320
|
+
raise NotFoundError(f"{self.model.__name__} not found for {filters}")
|
271
321
|
if self._has_soft_delete() and not include_deleted and getattr(obj, "is_deleted", False):
|
272
|
-
raise NotFoundError(f"{self.model.__name__}({
|
322
|
+
raise NotFoundError(f"{self.model.__name__}({getattr(obj, 'id', '?')}) deleted")
|
273
323
|
return obj
|
274
324
|
|
275
|
-
async def try_get(self, id_: Any, *, include_deleted: bool = False) -> Optional[T]:
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
325
|
+
async def try_get(self, id_: Any = None, *, include_deleted: bool = False, **filters) -> Optional[T]:
|
326
|
+
"""
|
327
|
+
Как get(), но не выбрасывает исключение при отсутствии объекта.
|
328
|
+
"""
|
329
|
+
try:
|
330
|
+
return await self.get(id_=id_, include_deleted=include_deleted, **filters)
|
331
|
+
except NotFoundError:
|
280
332
|
return None
|
281
|
-
return obj
|
282
333
|
|
283
334
|
async def add(self, entity: T) -> T:
|
284
335
|
self.session.add(entity)
|
@@ -294,10 +345,10 @@ class SAAsyncRepository(Generic[T]):
|
|
294
345
|
async def remove(self, entity: Optional[T] = None, id: Optional[Any] = None) -> bool:
|
295
346
|
if entity is None and id is None:
|
296
347
|
raise ValueError("remove() requires either entity or id")
|
297
|
-
|
348
|
+
|
298
349
|
if id is not None:
|
299
350
|
return await self._delete_by_id(id)
|
300
|
-
|
351
|
+
|
301
352
|
insp = inspect(entity, raiseerr=False)
|
302
353
|
if not (insp and (insp.persistent or insp.pending)):
|
303
354
|
pk = getattr(entity, "id", None)
|
@@ -310,10 +361,9 @@ class SAAsyncRepository(Generic[T]):
|
|
310
361
|
setattr(entity, "is_deleted", True)
|
311
362
|
else:
|
312
363
|
await self.session.delete(entity)
|
313
|
-
|
364
|
+
|
314
365
|
return True
|
315
366
|
|
316
|
-
|
317
367
|
async def _delete_by_id(self, id_: Any) -> bool:
|
318
368
|
obj = await self.session.get(self.model, id_)
|
319
369
|
if not obj:
|
@@ -321,7 +371,8 @@ class SAAsyncRepository(Generic[T]):
|
|
321
371
|
await self.remove(obj)
|
322
372
|
return True
|
323
373
|
|
324
|
-
async def page(self, page: PageRequest, spec: Optional[Spec] = None, order_by=None, *,
|
374
|
+
async def page(self, page: PageRequest, spec: Optional[Spec] = None, order_by=None, *,
|
375
|
+
include_deleted: bool = False) -> Page[T]: # type: ignore
|
325
376
|
base = self._select()
|
326
377
|
if spec:
|
327
378
|
base = spec(base)
|
@@ -340,14 +391,14 @@ class SAAsyncRepository(Generic[T]):
|
|
340
391
|
return Page(items, total, page.page, page.size)
|
341
392
|
|
342
393
|
async def get_all_by_column(
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
394
|
+
self,
|
395
|
+
column_name: str,
|
396
|
+
value: Any,
|
397
|
+
*,
|
398
|
+
limit: Optional[int] = None,
|
399
|
+
order_by=None,
|
400
|
+
include_deleted: bool = False,
|
401
|
+
**extra_filters
|
351
402
|
) -> list[T]:
|
352
403
|
col = self._resolve_column(column_name)
|
353
404
|
stmt = select(self.model).where(col == value)
|
@@ -363,11 +414,11 @@ class SAAsyncRepository(Generic[T]):
|
|
363
414
|
# Alias
|
364
415
|
async def find_all_by_column(self, *args, **kwargs):
|
365
416
|
return await self.get_all_by_column(*args, **kwargs)
|
366
|
-
|
417
|
+
|
367
418
|
async def get_or_create(
|
368
|
-
|
369
|
-
|
370
|
-
|
419
|
+
self,
|
420
|
+
defaults: Optional[dict] = None,
|
421
|
+
**unique_filters
|
371
422
|
) -> tuple[T, bool]:
|
372
423
|
stmt = select(self.model).filter_by(**unique_filters)
|
373
424
|
if hasattr(self.model, "is_deleted"):
|
@@ -437,4 +488,4 @@ class SAAsyncRepository(Generic[T]):
|
|
437
488
|
setattr(obj, "is_deleted", False)
|
438
489
|
await self.session.flush()
|
439
490
|
return True
|
440
|
-
return False
|
491
|
+
return False
|
@@ -0,0 +1,12 @@
|
|
1
|
+
SARepo/__init__.py,sha256=tNYbuUDloC1qVnXq7uomS3jRcaPUvy0VEZiMdYR6x1M,183
|
2
|
+
SARepo/base.py,sha256=UbAdZ9WYh_o93mrCVL3D8Q0tY_8mvm0HspO_L5m0GTQ,874
|
3
|
+
SARepo/models.py,sha256=ypSmbKAijvNH4WjSeJBgyNT8mKa_e_7-I5kiNisjGAI,570
|
4
|
+
SARepo/repo.py,sha256=2MsR21GFMYstXRimFVqShC9foG7KmAuzTFtqsjK3a-Q,2129
|
5
|
+
SARepo/sa_repo.py,sha256=fUae3B1aDxN3E-oKuthi0joWnrpCAOaUFHCi9H3be8c,19052
|
6
|
+
SARepo/specs.py,sha256=e-X1cCkva3e71M37hcfbjNDMezZAaOkkRaPToUzhEeU,687
|
7
|
+
SARepo/uow.py,sha256=yPlvi7PoH9x2pLNt6hEin9zT5qG9axUKn_TkZcIuz1s,859
|
8
|
+
sarepo-0.1.7.dist-info/licenses/LICENSE,sha256=px90NOQCrnZ77qoFT8IvAiNS1CXQ2OU27uVBKbki9DM,131
|
9
|
+
sarepo-0.1.7.dist-info/METADATA,sha256=b3pMl6G85wJ8qN1LtB5dc-mGwj0vmQXWBaL66x2WazY,2691
|
10
|
+
sarepo-0.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
11
|
+
sarepo-0.1.7.dist-info/top_level.txt,sha256=0k952UYVZLIIv3kZzFlxI0yzBMusZo3-XCQtxms_kS0,7
|
12
|
+
sarepo-0.1.7.dist-info/RECORD,,
|
sarepo-0.1.6.dist-info/RECORD
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
SARepo/__init__.py,sha256=tNYbuUDloC1qVnXq7uomS3jRcaPUvy0VEZiMdYR6x1M,183
|
2
|
-
SARepo/base.py,sha256=UbAdZ9WYh_o93mrCVL3D8Q0tY_8mvm0HspO_L5m0GTQ,874
|
3
|
-
SARepo/models.py,sha256=ypSmbKAijvNH4WjSeJBgyNT8mKa_e_7-I5kiNisjGAI,570
|
4
|
-
SARepo/repo.py,sha256=q99K9odCVuxJ9HIKwgVpu3p9-hyzxmGlE-wkiCp9tbg,1820
|
5
|
-
SARepo/sa_repo.py,sha256=iPJan94ALJ3N58aU1D6rgO6uhqLDUxcHXvORpQrAt9c,17258
|
6
|
-
SARepo/specs.py,sha256=e-X1cCkva3e71M37hcfbjNDMezZAaOkkRaPToUzhEeU,687
|
7
|
-
SARepo/uow.py,sha256=yPlvi7PoH9x2pLNt6hEin9zT5qG9axUKn_TkZcIuz1s,859
|
8
|
-
sarepo-0.1.6.dist-info/licenses/LICENSE,sha256=px90NOQCrnZ77qoFT8IvAiNS1CXQ2OU27uVBKbki9DM,131
|
9
|
-
sarepo-0.1.6.dist-info/METADATA,sha256=4Yfl0Eh0h8Sb2Lrs_DxZRfPJuZzGTg7-I9blRPva9aw,2691
|
10
|
-
sarepo-0.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
11
|
-
sarepo-0.1.6.dist-info/top_level.txt,sha256=0k952UYVZLIIv3kZzFlxI0yzBMusZo3-XCQtxms_kS0,7
|
12
|
-
sarepo-0.1.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|