fast-clean 0.4.0__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.
Files changed (65) hide show
  1. fast_clean/__init__.py +3 -0
  2. fast_clean/broker.py +123 -0
  3. fast_clean/container.py +235 -0
  4. fast_clean/contrib/__init__.py +3 -0
  5. fast_clean/contrib/healthcheck/__init__.py +3 -0
  6. fast_clean/contrib/healthcheck/router.py +17 -0
  7. fast_clean/db.py +179 -0
  8. fast_clean/depends.py +255 -0
  9. fast_clean/enums.py +39 -0
  10. fast_clean/exceptions.py +281 -0
  11. fast_clean/loggers.py +26 -0
  12. fast_clean/middleware.py +20 -0
  13. fast_clean/models.py +33 -0
  14. fast_clean/py.typed +0 -0
  15. fast_clean/redis.py +23 -0
  16. fast_clean/repositories/__init__.py +30 -0
  17. fast_clean/repositories/cache/__init__.py +83 -0
  18. fast_clean/repositories/cache/in_memory.py +62 -0
  19. fast_clean/repositories/cache/redis.py +58 -0
  20. fast_clean/repositories/crud/__init__.py +149 -0
  21. fast_clean/repositories/crud/db.py +559 -0
  22. fast_clean/repositories/crud/in_memory.py +369 -0
  23. fast_clean/repositories/crud/type_vars.py +35 -0
  24. fast_clean/repositories/settings/__init__.py +52 -0
  25. fast_clean/repositories/settings/enums.py +16 -0
  26. fast_clean/repositories/settings/env.py +55 -0
  27. fast_clean/repositories/settings/exceptions.py +13 -0
  28. fast_clean/repositories/settings/type_vars.py +9 -0
  29. fast_clean/repositories/storage/__init__.py +114 -0
  30. fast_clean/repositories/storage/enums.py +20 -0
  31. fast_clean/repositories/storage/local.py +118 -0
  32. fast_clean/repositories/storage/reader.py +122 -0
  33. fast_clean/repositories/storage/s3.py +118 -0
  34. fast_clean/repositories/storage/schemas.py +31 -0
  35. fast_clean/schemas/__init__.py +25 -0
  36. fast_clean/schemas/exceptions.py +32 -0
  37. fast_clean/schemas/pagination.py +65 -0
  38. fast_clean/schemas/repository.py +43 -0
  39. fast_clean/schemas/request_response.py +36 -0
  40. fast_clean/schemas/status_response.py +13 -0
  41. fast_clean/services/__init__.py +16 -0
  42. fast_clean/services/cryptography/__init__.py +57 -0
  43. fast_clean/services/cryptography/aes.py +120 -0
  44. fast_clean/services/cryptography/enums.py +20 -0
  45. fast_clean/services/lock.py +57 -0
  46. fast_clean/services/seed.py +91 -0
  47. fast_clean/services/transaction.py +40 -0
  48. fast_clean/settings.py +189 -0
  49. fast_clean/tools/__init__.py +6 -0
  50. fast_clean/tools/cryptography.py +56 -0
  51. fast_clean/tools/load_seed.py +31 -0
  52. fast_clean/use_cases.py +38 -0
  53. fast_clean/utils/__init__.py +15 -0
  54. fast_clean/utils/process.py +31 -0
  55. fast_clean/utils/pydantic.py +23 -0
  56. fast_clean/utils/ssl_context.py +31 -0
  57. fast_clean/utils/string.py +28 -0
  58. fast_clean/utils/thread.py +21 -0
  59. fast_clean/utils/time.py +14 -0
  60. fast_clean/utils/type_converters.py +18 -0
  61. fast_clean/utils/typer.py +23 -0
  62. fast_clean-0.4.0.dist-info/METADATA +38 -0
  63. fast_clean-0.4.0.dist-info/RECORD +65 -0
  64. fast_clean-0.4.0.dist-info/WHEEL +5 -0
  65. fast_clean-0.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,559 @@
1
+ """
2
+ Модуль, содержащий репозиторий для выполнения CRUD операций над моделями в базе данных.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import contextlib
8
+ import uuid
9
+ from collections.abc import Callable, Iterable, Sequence
10
+ from itertools import groupby
11
+ from typing import Any, Generic, Self, cast, get_args
12
+
13
+ import sqlalchemy as sa
14
+ from sqlalchemy.dialects.postgresql import insert
15
+ from sqlalchemy.exc import IntegrityError
16
+ from sqlalchemy.ext.asyncio import AsyncSession
17
+ from sqlalchemy.orm import selectin_polymorphic
18
+ from sqlalchemy.sql.expression import func
19
+
20
+ from fast_clean.db import SessionManagerProtocol
21
+ from fast_clean.enums import ModelActionEnum
22
+ from fast_clean.exceptions import (
23
+ ModelIntegrityError,
24
+ ModelNotFoundError,
25
+ SortingFieldNotFoundError,
26
+ )
27
+ from fast_clean.schemas import PaginationResultSchema, PaginationSchema
28
+
29
+ from .type_vars import (
30
+ CreateSchemaBaseType,
31
+ CreateSchemaOldType,
32
+ CreateSchemaType,
33
+ IdType,
34
+ ModelBaseType,
35
+ ModelOldType,
36
+ ModelType,
37
+ ReadSchemaBaseType,
38
+ ReadSchemaOldType,
39
+ ReadSchemaType,
40
+ UpdateSchemaBaseType,
41
+ UpdateSchemaOldType,
42
+ UpdateSchemaType,
43
+ )
44
+
45
+
46
+ class DbCrudRepositoryBase(
47
+ Generic[
48
+ ModelBaseType,
49
+ ReadSchemaBaseType,
50
+ CreateSchemaBaseType,
51
+ UpdateSchemaBaseType,
52
+ IdType,
53
+ ]
54
+ ):
55
+ """
56
+ Базовый репозиторий для выполнения CRUD операций над моделями в базе данных.
57
+ """
58
+
59
+ __abstract__: bool = True
60
+
61
+ __orig_bases__: tuple[
62
+ type[
63
+ DbCrudRepositoryBase[ModelBaseType, ReadSchemaBaseType, CreateSchemaBaseType, UpdateSchemaBaseType, IdType]
64
+ ]
65
+ ]
66
+ __subtypes__: Sequence[
67
+ tuple[type[ModelBaseType], type[ReadSchemaBaseType], type[CreateSchemaBaseType], type[UpdateSchemaBaseType]]
68
+ ]
69
+
70
+ model_types: set[type[ModelBaseType]] = set()
71
+ model_subtypes: set[type[ModelBaseType]] = set()
72
+ model_types_mapping: dict[type[ModelBaseType], type[ReadSchemaBaseType]]
73
+ create_models_mapping: dict[type[CreateSchemaBaseType], type[ModelBaseType]]
74
+ update_models_mapping: dict[type[UpdateSchemaBaseType], type[ModelBaseType]]
75
+ model_identities_mapping: dict[Any, type[ModelBaseType]] = {}
76
+
77
+ model_type: type[ModelBaseType]
78
+
79
+ def __init__(self, session_manager: SessionManagerProtocol):
80
+ if self.__dict__.get('__abstract__', False):
81
+ raise TypeError(f"Can't instantiate abstract class {type(self).__name__}")
82
+ self.session_manager = session_manager
83
+
84
+ def __init_subclass__(cls) -> None:
85
+ """
86
+ Инициализируем класс.
87
+
88
+ Получаем используемую модель SQLAlchemy и схему Pydantic из базового типа.
89
+ """
90
+ if cls.__dict__.get('__abstract__', False):
91
+ return super().__init_subclass__()
92
+
93
+ base_repository_generic = next(
94
+ (
95
+ base
96
+ for base in getattr(cls, '__orig_bases__', [])
97
+ if issubclass(getattr(base, '__origin__', base), DbCrudRepositoryBase)
98
+ ),
99
+ None,
100
+ )
101
+ if not base_repository_generic:
102
+ raise ValueError('Repository must be implemented by DbCrudRepositoryBase')
103
+
104
+ if not hasattr(cls, '__subtypes__'):
105
+ cls.__subtypes__ = []
106
+
107
+ cls.model_subtypes = {st[0] for st in cls.__subtypes__}
108
+
109
+ cls.model_types_mapping = {}
110
+ cls.create_models_mapping = {}
111
+ cls.update_models_mapping = {}
112
+ cls.model_identities_mapping = {}
113
+ types: Sequence[
114
+ tuple[type[ModelBaseType], type[ReadSchemaBaseType], type[CreateSchemaBaseType], type[UpdateSchemaBaseType]]
115
+ ] = [*cls.__subtypes__, get_args(base_repository_generic)[:4]]
116
+ for model_type, read_schema_type, create_schema_type, update_schema_type in types:
117
+ cls.model_types.add(model_type)
118
+ cls.model_types_mapping[model_type] = read_schema_type
119
+ cls.create_models_mapping[create_schema_type] = model_type
120
+ cls.update_models_mapping[update_schema_type] = model_type
121
+ cls.model_identities_mapping[model_type.__mapper__.polymorphic_identity] = model_type
122
+
123
+ cls.model_type, *_ = cast(
124
+ tuple[
125
+ type[ModelBaseType],
126
+ type[ReadSchemaBaseType],
127
+ type[CreateSchemaBaseType],
128
+ type[UpdateSchemaBaseType],
129
+ ],
130
+ get_args(base_repository_generic),
131
+ )
132
+
133
+ return super().__init_subclass__()
134
+
135
+ async def get(self: Self, id: IdType) -> ReadSchemaBaseType:
136
+ """
137
+ Получаем модель по идентификатору.
138
+ """
139
+ async with self.session_manager.get_session() as s:
140
+ statement = self.select().where(self.model_type.id == id)
141
+ model = (await s.execute(statement)).scalar_one_or_none()
142
+ if model is None:
143
+ raise ModelNotFoundError(self.model_type, model_id=id)
144
+ return self.model_validate(model)
145
+
146
+ async def get_or_none(self: Self, id: IdType) -> ReadSchemaBaseType | None:
147
+ """
148
+ Получаем модель или None по идентификатору.
149
+ """
150
+ with contextlib.suppress(ModelNotFoundError):
151
+ return await self.get(id)
152
+ return None
153
+
154
+ async def get_by_ids(self: Self, ids: Sequence[IdType], *, exact: bool = False) -> list[ReadSchemaBaseType]:
155
+ """
156
+ Получаем список моделей по идентификаторам.
157
+ """
158
+ async with self.session_manager.get_session() as s:
159
+ statement = self.select().where(self.model_type.id.in_(ids))
160
+ models = (await s.execute(statement)).scalars().all()
161
+ self.check_get_by_ids_exact(ids, models, exact)
162
+ return [self.model_validate(model) for model in models]
163
+
164
+ async def get_all(self: Self) -> list[ReadSchemaBaseType]:
165
+ """
166
+ Получаем все модели.
167
+ """
168
+ async with self.session_manager.get_session() as s:
169
+ statement = self.select()
170
+ models = (await s.execute(statement)).scalars().all()
171
+ return [self.model_validate(model) for model in models]
172
+
173
+ async def paginate(
174
+ self: Self,
175
+ pagination: PaginationSchema,
176
+ user: Any,
177
+ policies: list[str],
178
+ *,
179
+ search: str | None = None,
180
+ search_by: Iterable[str] | None = None,
181
+ sorting: Iterable[str] | None = None,
182
+ ) -> PaginationResultSchema[ReadSchemaBaseType]:
183
+ """
184
+ Получаем список моделей с пагинацией, поиском и сортировкой.
185
+ """
186
+ return await self.paginate_with_filter(
187
+ pagination,
188
+ user,
189
+ policies,
190
+ search=search,
191
+ search_by=search_by,
192
+ sorting=sorting,
193
+ )
194
+
195
+ async def create(self: Self, create_object: CreateSchemaBaseType) -> ReadSchemaBaseType:
196
+ """
197
+ Создаем модель.
198
+ """
199
+ async with self.session_manager.get_session() as s:
200
+ try:
201
+ model_type = self.create_models_mapping[type(create_object)]
202
+ create_dict = self.dump_create_object(create_object)
203
+ return (await self.bulk_create_with_model_type(model_type, [create_dict], s))[0]
204
+ except IntegrityError as integrity_error:
205
+ raise ModelIntegrityError(self.model_type, ModelActionEnum.INSERT) from integrity_error
206
+
207
+ async def bulk_create(self: Self, create_objects: list[CreateSchemaBaseType]) -> list[ReadSchemaBaseType]:
208
+ """
209
+ Создаем несколько моделей.
210
+ """
211
+ if len(create_objects) == 0:
212
+ return []
213
+ async with self.session_manager.get_session() as s:
214
+ try:
215
+ created_models: list[ReadSchemaBaseType] = []
216
+ for model_type, type_create_objects in groupby(
217
+ create_objects, key=lambda co: self.create_models_mapping[type(co)]
218
+ ):
219
+ create_dicts = [self.dump_create_object(create_object) for create_object in type_create_objects]
220
+ created_models.extend(await self.bulk_create_with_model_type(model_type, create_dicts, s))
221
+ return created_models
222
+ except IntegrityError as integrity_error:
223
+ raise ModelIntegrityError(self.model_type, ModelActionEnum.INSERT) from integrity_error
224
+
225
+ async def update(self: Self, update_object: UpdateSchemaBaseType) -> ReadSchemaBaseType:
226
+ """
227
+ Обновляем модель.
228
+ """
229
+ async with self.session_manager.get_session() as s:
230
+ try:
231
+ model_type = self.update_models_mapping[type(update_object)]
232
+ update_dict = update_object.model_dump(exclude_unset=True)
233
+ return await self.update_with_model_type(model_type, update_dict, s)
234
+ except IntegrityError as integrity_error:
235
+ raise ModelIntegrityError(self.model_type, ModelActionEnum.UPDATE) from integrity_error
236
+
237
+ async def bulk_update(self: Self, update_objects: list[UpdateSchemaBaseType]) -> None:
238
+ """
239
+ Обновляем несколько моделей.
240
+ """
241
+ if len(update_objects) == 0:
242
+ return
243
+ async with self.session_manager.get_session() as s:
244
+ try:
245
+ for model_type, type_update_objects in groupby(
246
+ update_objects, key=lambda co: self.update_models_mapping[type(co)]
247
+ ):
248
+ update_dicts = [update_object.model_dump() for update_object in type_update_objects]
249
+ await self.bulk_update_with_model_type(model_type, update_dicts, s)
250
+ except IntegrityError as integrity_error:
251
+ raise ModelIntegrityError(self.model_type, ModelActionEnum.UPDATE) from integrity_error
252
+
253
+ async def upsert(self: Self, create_object: CreateSchemaBaseType) -> ReadSchemaBaseType:
254
+ """
255
+ Создаем или обновляем модель.
256
+ """
257
+ async with self.session_manager.get_session() as s:
258
+ try:
259
+ model_type = self.create_models_mapping[type(create_object)]
260
+ create_dict = self.dump_create_object(create_object)
261
+ return await self.upsert_with_model_type(model_type, create_dict, s)
262
+ except IntegrityError as integrity_error:
263
+ raise ModelIntegrityError(self.model_type, ModelActionEnum.UPSERT) from integrity_error
264
+
265
+ async def delete(self: Self, ids: Sequence[IdType]) -> None:
266
+ """
267
+ Удаляем модели.
268
+ """
269
+ if len(ids) == 0:
270
+ return
271
+ async with self.session_manager.get_session() as s:
272
+ try:
273
+ model_types: list[type[ModelBaseType]] = [self.model_type]
274
+ if self.model_type.__mapper__.polymorphic_on is not None:
275
+ types_statement = (
276
+ sa.select(self.model_type.__mapper__.polymorphic_on)
277
+ .where(self.model_type.id.in_(ids))
278
+ .distinct()
279
+ )
280
+ types = (await s.execute(types_statement)).scalars().all()
281
+ for t in types:
282
+ model_types.append(self.model_identities_mapping[t])
283
+ for model_type in model_types[::-1]:
284
+ statement = sa.delete(model_type).where(model_type.id.in_(ids))
285
+ await s.execute(statement)
286
+ except IntegrityError as integrity_error:
287
+ raise ModelIntegrityError(self.model_type, ModelActionEnum.DELETE) from integrity_error
288
+
289
+ @classmethod
290
+ def select(cls) -> sa.Select[tuple[ModelBaseType]]:
291
+ """
292
+ Выбираем базовую модели или наследника со всеми полями при наличии.
293
+ """
294
+ statement = sa.select(cls.model_type)
295
+ if cls.model_subtypes:
296
+ return statement.options(selectin_polymorphic(cls.model_type, cls.model_subtypes))
297
+ return statement
298
+
299
+ @classmethod
300
+ def model_validate(cls, model: ModelBaseType) -> ReadSchemaBaseType:
301
+ """
302
+ Приводим модель к схеме.
303
+ """
304
+ read_schema_type = cls.model_types_mapping[type(model)]
305
+ return cast(
306
+ ReadSchemaBaseType,
307
+ read_schema_type.model_validate(model, from_attributes=True),
308
+ )
309
+
310
+ @classmethod
311
+ async def bulk_create_with_model_type(
312
+ cls, model_type: type[ModelBaseType], create_dicts: list[dict[str, Any]], session: AsyncSession
313
+ ) -> list[ReadSchemaBaseType]:
314
+ """
315
+ Создаем модели с помощью типа.
316
+ """
317
+ parent_dicts = await cls.bulk_create_parent_model(model_type, create_dicts, session)
318
+ values: list[dict[str, Any]] = []
319
+ for create_dict, parent_dict in zip(create_dicts, parent_dicts, strict=True):
320
+ value = {k: v for k, v in create_dict.items() if k in model_type.__table__.columns}
321
+ if 'id' in parent_dict:
322
+ value['id'] = parent_dict['id']
323
+ values.append(value)
324
+ statement = sa.insert(model_type).values(values).returning(*model_type.__table__.columns.values())
325
+ model_dicts = (await session.execute(statement)).mappings().all()
326
+ read_schema_type = cls.model_types_mapping[model_type]
327
+ return [
328
+ cast(
329
+ ReadSchemaBaseType,
330
+ read_schema_type.model_validate({**parent_dict, **model_dict}),
331
+ )
332
+ for parent_dict, model_dict in zip(parent_dicts, model_dicts, strict=True)
333
+ ]
334
+
335
+ @classmethod
336
+ async def bulk_create_parent_model(
337
+ cls, model_type: type[ModelBaseType], create_dicts: list[dict[str, Any]], session: AsyncSession
338
+ ) -> list[dict[str, Any]]:
339
+ """
340
+ Создаем родительские модели с помощью типа.
341
+ """
342
+ parent_model_type = cls.get_parent_model_type(model_type)
343
+ if parent_model_type is None:
344
+ return [{} for _ in create_dicts]
345
+ return [
346
+ ps.model_dump() for ps in await cls.bulk_create_with_model_type(parent_model_type, create_dicts, session)
347
+ ]
348
+
349
+ @classmethod
350
+ async def update_with_model_type(
351
+ cls, model_type: type[ModelBaseType], update_dict: dict[str, Any], session: AsyncSession
352
+ ) -> ReadSchemaBaseType:
353
+ """
354
+ Обновляем модель с помощью типа.
355
+ """
356
+ parent_dict = await cls.update_parent_model(model_type, update_dict, session)
357
+ statement = (
358
+ sa.update(model_type)
359
+ .where(model_type.id == update_dict['id'])
360
+ .values({k: v for k, v in update_dict.items() if k in model_type.__table__.columns if k != 'id'})
361
+ .returning(*model_type.__table__.columns.values())
362
+ )
363
+ model_dict = (await session.execute(statement)).mappings().one()
364
+ read_schema_type = cls.model_types_mapping[model_type]
365
+ return cast(
366
+ ReadSchemaBaseType,
367
+ read_schema_type.model_validate({**parent_dict, **model_dict}),
368
+ )
369
+
370
+ @classmethod
371
+ async def update_parent_model(
372
+ cls, model_type: type[ModelBaseType], update_dict: dict[str, Any], session: AsyncSession
373
+ ) -> dict[str, Any]:
374
+ """
375
+ Обновляем родительскую модель с помощью типа.
376
+ """
377
+ parent_model_type = cls.get_parent_model_type(model_type)
378
+ if parent_model_type is None:
379
+ return {}
380
+ return (await cls.update_with_model_type(parent_model_type, update_dict, session)).model_dump()
381
+
382
+ @classmethod
383
+ async def bulk_update_with_model_type(
384
+ cls, model_type: type[ModelBaseType], update_dicts: list[dict[str, Any]], session: AsyncSession
385
+ ) -> None:
386
+ """
387
+ Создаем модели с помощью типа.
388
+ """
389
+ await cls.bulk_update_parent_model(model_type, update_dicts, session)
390
+ values: list[dict[str, Any]] = []
391
+ for update_dict in update_dicts:
392
+ values.append({k: v for k, v in update_dict.items() if k in model_type.__table__.columns})
393
+ statement = sa.update(model_type)
394
+ await session.execute(statement, values)
395
+
396
+ @classmethod
397
+ async def bulk_update_parent_model(
398
+ cls, model_type: type[ModelBaseType], update_dicts: list[dict[str, Any]], session: AsyncSession
399
+ ) -> None:
400
+ """
401
+ Обновляем родительские модели с помощью типа.
402
+ """
403
+ parent_model_type = cls.get_parent_model_type(model_type)
404
+ if parent_model_type is not None:
405
+ await cls.bulk_update_with_model_type(parent_model_type, update_dicts, session)
406
+
407
+ @classmethod
408
+ async def upsert_with_model_type(
409
+ cls, model_type: type[ModelBaseType], create_dict: dict[str, Any], session: AsyncSession
410
+ ) -> ReadSchemaBaseType:
411
+ """
412
+ Создаем или обновляем модель с помощью типа.
413
+ """
414
+ parent_dict = await cls.upsert_parent_model(model_type, create_dict, session)
415
+ primary_keys = {key.name for key in cast(Any, sa.inspect(model_type)).primary_key}
416
+ values = {k: v for k, v in create_dict.items() if k in model_type.__table__.columns}
417
+ statement = (
418
+ insert(model_type)
419
+ .values(values)
420
+ .on_conflict_do_update(
421
+ index_elements=primary_keys,
422
+ set_={k: v for k, v in values.items() if k not in primary_keys},
423
+ )
424
+ .returning(*model_type.__table__.columns.values())
425
+ )
426
+ model_dict = (await session.execute(statement)).mappings().one()
427
+ read_schema_type = cls.model_types_mapping[model_type]
428
+ return cast(
429
+ ReadSchemaBaseType,
430
+ read_schema_type.model_validate({**parent_dict, **model_dict}),
431
+ )
432
+
433
+ @classmethod
434
+ async def upsert_parent_model(
435
+ cls, model_type: type[ModelBaseType], create_dict: dict[str, Any], session: AsyncSession
436
+ ) -> dict[str, Any]:
437
+ """
438
+ Создаем или обновляем родительскую модель с помощью типа.
439
+ """
440
+ parent_model_type = cls.get_parent_model_type(model_type)
441
+ if parent_model_type is None:
442
+ return {}
443
+ return (await cls.upsert_with_model_type(parent_model_type, create_dict, session)).model_dump()
444
+
445
+ @staticmethod
446
+ def dump_create_object(create_object: CreateSchemaBaseType) -> dict[str, Any]:
447
+ """
448
+ Создаем словарь для схемы создания модели.
449
+ """
450
+ create_dict = create_object.model_dump()
451
+ if create_dict['id'] is None:
452
+ del create_dict['id']
453
+ return create_dict
454
+
455
+ @classmethod
456
+ def get_parent_model_type(cls, model_type: type[ModelBaseType]) -> type[ModelBaseType] | None:
457
+ """
458
+ Получаем тип родительской модели.
459
+ """
460
+ if not model_type.__bases__ or model_type.__bases__[0] not in cls.model_types:
461
+ return None
462
+ return model_type.__bases__[0]
463
+
464
+ @classmethod
465
+ def check_get_by_ids_exact(cls, ids: Sequence[IdType], models: Sequence[ModelBaseType], exact: bool) -> None:
466
+ """
467
+ Проверяем, что по идентификаторам получены все модели.
468
+ """
469
+ if exact and len(ids) != len(models):
470
+ raise ModelNotFoundError(
471
+ cls.model_type,
472
+ model_id=set(ids) - {cast(IdType, model.id) for model in models},
473
+ )
474
+
475
+ async def paginate_with_filter(
476
+ self: Self,
477
+ pagination: PaginationSchema,
478
+ user: Any,
479
+ policies: list[str],
480
+ *,
481
+ search: str | None = None,
482
+ search_by: Iterable[str] | None = None,
483
+ sorting: Iterable[str] | None = None,
484
+ select_filter: Callable[[sa.Select[tuple[ModelBaseType]]], sa.Select[tuple[ModelBaseType]]] | None = None,
485
+ ) -> PaginationResultSchema[ReadSchemaBaseType]:
486
+ """
487
+ Получаем список моделей с пагинацией, поиском, сортировкой и фильтрами.
488
+ """
489
+ if len(policies) == 0:
490
+ return PaginationResultSchema(objects=[], count=0)
491
+ search_by = search_by or []
492
+ sorting = sorting or []
493
+ async with self.session_manager.get_session() as s:
494
+ statement = self.select()
495
+ if select_filter:
496
+ statement = select_filter(statement)
497
+ if search:
498
+ search_where: sa.ColumnElement[Any] = sa.false()
499
+ for sb in search_by:
500
+ search_where = sa.or_(search_where, getattr(self.model_type, sb).ilike(f'%{search}%'))
501
+ statement = statement.where(search_where)
502
+ order_by_expr = self.get_order_by_expr(sorting)
503
+ models = (
504
+ (await s.execute(statement.limit(pagination.limit).offset(pagination.offset).order_by(*order_by_expr)))
505
+ .scalars()
506
+ .all()
507
+ )
508
+ objects = [self.model_validate(model) for model in models]
509
+ count_statement = statement.with_only_columns(func.count(self.model_type.id))
510
+ count = (await s.execute(count_statement)).scalar_one()
511
+ return PaginationResultSchema(count=count, objects=objects)
512
+
513
+ def get_order_by_expr(self: Self, sorting: Iterable[str]) -> list[sa.UnaryExpression]:
514
+ """
515
+ Получаем выражение сортировки.
516
+ """
517
+ order_by_expr: list[sa.UnaryExpression] = []
518
+ for st in sorting:
519
+ try:
520
+ if st[0] == '-':
521
+ order_by_expr.append(getattr(self.model_type, st[1:]).desc())
522
+ else:
523
+ order_by_expr.append(getattr(self.model_type, st))
524
+ except AttributeError as attribute_error:
525
+ raise SortingFieldNotFoundError(st) from attribute_error
526
+ return order_by_expr
527
+
528
+
529
+ class DbCrudRepositoryOld(
530
+ DbCrudRepositoryBase[
531
+ ModelOldType,
532
+ ReadSchemaOldType,
533
+ CreateSchemaOldType,
534
+ UpdateSchemaOldType,
535
+ int,
536
+ ],
537
+ Generic[
538
+ ModelOldType,
539
+ ReadSchemaOldType,
540
+ CreateSchemaOldType,
541
+ UpdateSchemaOldType,
542
+ ],
543
+ ):
544
+ """
545
+ Репозиторий для выполнения CRUD операций над моделями старого типа в базе данных.
546
+ """
547
+
548
+ __abstract__ = True
549
+
550
+
551
+ class DbCrudRepository(
552
+ DbCrudRepositoryBase[ModelType, ReadSchemaType, CreateSchemaType, UpdateSchemaType, uuid.UUID],
553
+ Generic[ModelType, ReadSchemaType, CreateSchemaType, UpdateSchemaType],
554
+ ):
555
+ """
556
+ Репозиторий для выполнения CRUD операций над моделями нового типа в базе данных.
557
+ """
558
+
559
+ __abstract__ = True