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,369 @@
1
+ """
2
+ Модуль, содержащий репозиторий для выполнения CRUD операций над моделями в памяти.
3
+ """
4
+
5
+ import contextlib
6
+ import uuid
7
+ from abc import ABC, abstractmethod
8
+ from collections.abc import Iterable, Sequence
9
+ from itertools import groupby
10
+ from typing import Any, Callable, Generic, Self, cast, get_args
11
+
12
+ from fast_clean.enums import ModelActionEnum
13
+ from fast_clean.exceptions import ModelIntegrityError, ModelNotFoundError
14
+ from fast_clean.schemas import PaginationResultSchema, PaginationSchema
15
+
16
+ from .type_vars import (
17
+ CreateSchemaBaseType,
18
+ CreateSchemaOldType,
19
+ CreateSchemaType,
20
+ IdType,
21
+ ReadSchemaBaseType,
22
+ ReadSchemaOldType,
23
+ ReadSchemaType,
24
+ UpdateSchemaBaseType,
25
+ UpdateSchemaOldType,
26
+ UpdateSchemaType,
27
+ )
28
+
29
+
30
+ class InMemoryCrudRepositoryBase(ABC, Generic[ReadSchemaBaseType, CreateSchemaBaseType, UpdateSchemaBaseType, IdType]):
31
+ """
32
+ Базовый репозиторий для выполнения CRUD операций над моделями в памяти.
33
+ """
34
+
35
+ __abstract__: bool = True
36
+
37
+ __orig_bases__: 'tuple[type[InMemoryCrudRepositoryBase[ReadSchemaBaseType, CreateSchemaBaseType, UpdateSchemaBaseType, IdType]]]'
38
+ __subtypes__: Sequence[tuple[type[ReadSchemaBaseType], type[CreateSchemaBaseType], type[UpdateSchemaBaseType]]]
39
+
40
+ create_to_read_schemas_mapping: dict[type[CreateSchemaBaseType], type[ReadSchemaBaseType]]
41
+ create_to_update_schemas_mapping: dict[type[CreateSchemaBaseType], type[UpdateSchemaBaseType]]
42
+ update_to_read_schemas_mapping: dict[type[UpdateSchemaBaseType], type[ReadSchemaBaseType]]
43
+
44
+ read_schema_type: type[ReadSchemaBaseType]
45
+
46
+ def __init__(self) -> None:
47
+ if self.__dict__.get('__abstract__', False):
48
+ raise TypeError(f"Can't instantiate abstract class {type(self).__name__}")
49
+ self.models: dict[IdType, ReadSchemaBaseType] = {}
50
+
51
+ def __init_subclass__(cls) -> None:
52
+ """
53
+ Инициализируем класс.
54
+
55
+ Получаем схемы Pydantic из базового класса.
56
+ """
57
+ if cls.__dict__.get('__abstract__', False):
58
+ return super().__init_subclass__()
59
+
60
+ base_repository_generic = next(
61
+ (
62
+ base
63
+ for base in getattr(cls, '__orig_bases__', [])
64
+ if issubclass(getattr(base, '__origin__', base), InMemoryCrudRepositoryBase)
65
+ ),
66
+ None,
67
+ )
68
+ if not base_repository_generic:
69
+ raise ValueError('Repository must be implemented by InMemoryCrudRepositoryBase')
70
+
71
+ if not hasattr(cls, '__subtypes__'):
72
+ cls.__subtypes__ = []
73
+
74
+ cls.create_to_read_schemas_mapping = {}
75
+ cls.create_to_update_schemas_mapping = {}
76
+ cls.update_to_read_schemas_mapping = {}
77
+ types: Sequence[tuple[type[ReadSchemaBaseType], type[CreateSchemaBaseType], type[UpdateSchemaBaseType]]] = [
78
+ *cls.__subtypes__,
79
+ get_args(base_repository_generic)[:3],
80
+ ]
81
+ for read_schema_type, create_schema_type, update_schema_type in types:
82
+ cls.create_to_read_schemas_mapping[create_schema_type] = read_schema_type
83
+ cls.create_to_update_schemas_mapping[create_schema_type] = update_schema_type
84
+ cls.update_to_read_schemas_mapping[update_schema_type] = read_schema_type
85
+
86
+ cls.read_schema_type, *_ = cast(
87
+ tuple[
88
+ type[ReadSchemaBaseType],
89
+ type[CreateSchemaBaseType],
90
+ type[UpdateSchemaBaseType],
91
+ ],
92
+ get_args(base_repository_generic),
93
+ )
94
+ return super().__init_subclass__()
95
+
96
+ @abstractmethod
97
+ def generate_id(self: Self) -> IdType:
98
+ """
99
+ Генерируем идентификатор.
100
+ """
101
+ ...
102
+
103
+ def get_model_name(self: Self, read_schema_type: type[ReadSchemaBaseType] | None = None) -> str:
104
+ """
105
+ Получаем название модели.
106
+ """
107
+ read_schema_type = read_schema_type or self.read_schema_type
108
+ return read_schema_type.__name__.replace('Schema', '')
109
+
110
+ async def get(self: Self, id: IdType) -> ReadSchemaBaseType:
111
+ """
112
+ Получаем модель по идентификатору.
113
+ """
114
+ model = self.models.get(id)
115
+ if model is None:
116
+ raise ModelNotFoundError(self.get_model_name(), model_id=id)
117
+ return model
118
+
119
+ async def get_or_none(self: Self, id: IdType) -> ReadSchemaBaseType | None:
120
+ """
121
+ Получаем модель или None по идентификатору.
122
+ """
123
+ with contextlib.suppress(ModelNotFoundError):
124
+ return await self.get(id)
125
+ return None
126
+
127
+ async def get_by_ids(self: Self, ids: Sequence[IdType], *, exact: bool = False) -> list[ReadSchemaBaseType]:
128
+ """
129
+ Получаем список моделей по идентификаторам.
130
+ """
131
+ models: list[ReadSchemaBaseType] = []
132
+ for id in ids:
133
+ model = self.models.get(id)
134
+ if model is not None:
135
+ models.append(model)
136
+ self.check_get_by_ids_exact(ids, models, exact)
137
+ return models
138
+
139
+ async def get_all(self: Self) -> list[ReadSchemaBaseType]:
140
+ """
141
+ Получаем все модели.
142
+ """
143
+ return list(self.models.values())
144
+
145
+ async def paginate(
146
+ self: Self,
147
+ pagination: PaginationSchema,
148
+ user: Any,
149
+ policies: list[str],
150
+ *,
151
+ search: str | None = None,
152
+ search_by: Iterable[str] | None = None,
153
+ sorting: Iterable[str] | None = None,
154
+ ) -> PaginationResultSchema[ReadSchemaBaseType]:
155
+ """
156
+ Получаем список моделей с пагинацией, поиском и сортировкой.
157
+ """
158
+ return self.paginate_with_filter(
159
+ pagination,
160
+ user,
161
+ policies,
162
+ search=search,
163
+ search_by=search_by,
164
+ sorting=sorting,
165
+ )
166
+
167
+ async def create(self: Self, create_object: CreateSchemaBaseType) -> ReadSchemaBaseType:
168
+ """
169
+ Создаем модель.
170
+ """
171
+ model = self.make_model(create_object)
172
+ self.models[cast(IdType, model.id)] = model
173
+ return model
174
+
175
+ async def bulk_create(self: Self, create_objects: list[CreateSchemaBaseType]) -> list[ReadSchemaBaseType]:
176
+ """
177
+ Создаем несколько моделей.
178
+ """
179
+ models: list[ReadSchemaBaseType] = []
180
+ for create_object in create_objects:
181
+ models.append(self.make_model(create_object))
182
+ for model in models:
183
+ self.models[cast(IdType, model.id)] = model
184
+ return models
185
+
186
+ async def update(self: Self, update_object: UpdateSchemaBaseType) -> ReadSchemaBaseType:
187
+ """
188
+ Обновляем модель.
189
+ """
190
+ read_schema_type = self.update_to_read_schemas_mapping[type(update_object)]
191
+ model = self.models.get(cast(IdType, update_object.id))
192
+ if model is None:
193
+ raise ModelNotFoundError(self.get_model_name(read_schema_type), model_id=update_object.id)
194
+ model = cast(
195
+ ReadSchemaBaseType,
196
+ read_schema_type.model_validate(
197
+ {
198
+ **model.model_dump(),
199
+ **update_object.model_dump(exclude={'id'}, exclude_unset=True),
200
+ }
201
+ ),
202
+ )
203
+ self.models[cast(IdType, model.id)] = model
204
+ return model
205
+
206
+ async def bulk_update(self: Self, update_objects: list[UpdateSchemaBaseType]) -> None:
207
+ """
208
+ Обновляем несколько моделей.
209
+ """
210
+ for update_object in update_objects:
211
+ await self.update(update_object)
212
+
213
+ async def upsert(self: Self, create_object: CreateSchemaBaseType) -> ReadSchemaBaseType:
214
+ """
215
+ Создаем или обновляем модель.
216
+ """
217
+ if create_object.id is None or create_object.id not in self.models:
218
+ return await self.create(create_object)
219
+ update_schema_type = self.create_to_update_schemas_mapping[type(create_object)]
220
+ await self.update(
221
+ cast(
222
+ UpdateSchemaBaseType,
223
+ update_schema_type.model_validate(create_object.model_dump()),
224
+ )
225
+ )
226
+ return self.models[cast(IdType, create_object.id)]
227
+
228
+ async def delete(self: Self, ids: Sequence[IdType]) -> None:
229
+ """
230
+ Удаляем модели.
231
+ """
232
+ for id in ids:
233
+ if id in self.models:
234
+ del self.models[id]
235
+
236
+ def check_get_by_ids_exact(
237
+ self: Self,
238
+ ids: Sequence[IdType],
239
+ models: Sequence[ReadSchemaBaseType],
240
+ exact: bool,
241
+ ) -> None:
242
+ """
243
+ Проверяем, что по идентификаторам получены все модели.
244
+ """
245
+ if exact and len(ids) != len(models):
246
+ raise ModelNotFoundError(
247
+ self.get_model_name(),
248
+ model_id=set(ids) - {cast(IdType, model.id) for model in models},
249
+ )
250
+
251
+ def paginate_with_filter(
252
+ self: Self,
253
+ pagination: PaginationSchema,
254
+ user: Any,
255
+ policies: list[str],
256
+ *,
257
+ search: str | None = None,
258
+ search_by: Iterable[str] | None = None,
259
+ sorting: Iterable[str] | None = None,
260
+ select_filter: Callable[[ReadSchemaBaseType], bool] | None = None,
261
+ ) -> PaginationResultSchema[ReadSchemaBaseType]:
262
+ """
263
+ Получаем список моделей с пагинацией, поиском, сортировкой и фильтрами.
264
+ """
265
+ if len(policies) == 0:
266
+ return PaginationResultSchema(objects=[], count=0)
267
+ search_by = search_by or []
268
+ sorting = sorting or []
269
+ models = list(filter(select_filter, self.models.values()) if select_filter else self.models.values())
270
+ if search:
271
+ search_models: list[ReadSchemaBaseType] = []
272
+ for model in models:
273
+ for sb in search_by:
274
+ if search in getattr(model, sb):
275
+ search_models.append(model)
276
+ models = search_models
277
+ models = self.sort(models, sorting)
278
+ return PaginationResultSchema(
279
+ objects=models[pagination.offset : pagination.offset + pagination.limit],
280
+ count=len(models),
281
+ )
282
+
283
+ @classmethod
284
+ def sort(cls, models: list[ReadSchemaBaseType], sorting: Iterable[str]) -> list[ReadSchemaBaseType]:
285
+ """
286
+ Сортируем модели.
287
+ """
288
+ if not sorting:
289
+ return models
290
+ st, *sorting = sorting
291
+ if st[0] == '-':
292
+ sorted_models = sorted(models, key=lambda model: getattr(model, st[1:]), reverse=True)
293
+ else:
294
+ sorted_models = sorted(models, key=lambda model: getattr(model, st))
295
+ result: list[ReadSchemaBaseType] = []
296
+ for _, group_models in groupby(sorted_models, lambda model: getattr(model, st[1:] if st[0] == '-' else st)):
297
+ result.extend(cls.sort(list(group_models), sorting))
298
+ return result
299
+
300
+ def make_model(self: Self, create_object: CreateSchemaBaseType) -> ReadSchemaBaseType:
301
+ """
302
+ Создаем модель без сохранения.
303
+ """
304
+ create_dict = create_object.model_dump()
305
+ if create_dict['id'] is None:
306
+ create_dict['id'] = self.generate_id()
307
+ read_schema_type = self.create_to_read_schemas_mapping[type(create_object)]
308
+ model = cast(ReadSchemaBaseType, read_schema_type.model_validate(create_dict))
309
+ if model.id in self.models:
310
+ raise ModelIntegrityError(self.get_model_name(read_schema_type), ModelActionEnum.INSERT)
311
+ return model
312
+
313
+
314
+ class InMemoryCrudRepositoryOld(
315
+ InMemoryCrudRepositoryBase[
316
+ ReadSchemaOldType,
317
+ CreateSchemaOldType,
318
+ UpdateSchemaOldType,
319
+ int,
320
+ ],
321
+ Generic[
322
+ ReadSchemaOldType,
323
+ CreateSchemaOldType,
324
+ UpdateSchemaOldType,
325
+ ],
326
+ ):
327
+ """
328
+ Репозиторий для выполнения CRUD операций над моделями старого типа в памяти.
329
+ """
330
+
331
+ __abstract__ = True
332
+
333
+ def __init__(self) -> None:
334
+ super().__init__()
335
+ self.ids_counter = 0
336
+
337
+ def generate_id(self: Self) -> int:
338
+ """
339
+ Генерируем идентификатор.
340
+ """
341
+ current_id = self.ids_counter
342
+ self.ids_counter += 1
343
+ return current_id
344
+
345
+
346
+ class InMemoryCrudRepository(
347
+ InMemoryCrudRepositoryBase[
348
+ ReadSchemaType,
349
+ CreateSchemaType,
350
+ UpdateSchemaType,
351
+ uuid.UUID,
352
+ ],
353
+ Generic[
354
+ ReadSchemaType,
355
+ CreateSchemaType,
356
+ UpdateSchemaType,
357
+ ],
358
+ ):
359
+ """
360
+ Репозиторий для выполнения CRUD операций над моделями нового типа в памяти.
361
+ """
362
+
363
+ __abstract__ = True
364
+
365
+ def generate_id(self: Self) -> uuid.UUID:
366
+ """
367
+ Генерируем идентификатор.
368
+ """
369
+ return uuid.uuid4()
@@ -0,0 +1,35 @@
1
+ """
2
+ Модуль, содержащий переменные типов.
3
+ """
4
+
5
+ import uuid
6
+ from typing import TypeVar
7
+
8
+ from fast_clean.db import Base, BaseOld
9
+ from fast_clean.schemas import (
10
+ CreateSchema,
11
+ CreateSchemaOld,
12
+ ReadSchema,
13
+ ReadSchemaOld,
14
+ UpdateSchema,
15
+ UpdateSchemaOld,
16
+ )
17
+
18
+ ModelBaseType = TypeVar('ModelBaseType', bound=BaseOld | Base)
19
+ CreateSchemaBaseType = TypeVar('CreateSchemaBaseType', bound=CreateSchemaOld | CreateSchema)
20
+ ReadSchemaBaseType = TypeVar('ReadSchemaBaseType', bound=ReadSchemaOld | ReadSchema)
21
+ UpdateSchemaBaseType = TypeVar('UpdateSchemaBaseType', bound=UpdateSchemaOld | UpdateSchema)
22
+ IdType = TypeVar('IdType', bound=int | uuid.UUID)
23
+ IdTypeContravariant = TypeVar('IdTypeContravariant', bound=int | uuid.UUID, contravariant=True)
24
+
25
+
26
+ ModelOldType = TypeVar('ModelOldType', bound=BaseOld)
27
+ CreateSchemaOldType = TypeVar('CreateSchemaOldType', bound=CreateSchemaOld)
28
+ ReadSchemaOldType = TypeVar('ReadSchemaOldType', bound=ReadSchemaOld)
29
+ UpdateSchemaOldType = TypeVar('UpdateSchemaOldType', bound=UpdateSchemaOld)
30
+
31
+
32
+ ModelType = TypeVar('ModelType', bound=Base)
33
+ CreateSchemaType = TypeVar('CreateSchemaType', bound=CreateSchema)
34
+ ReadSchemaType = TypeVar('ReadSchemaType', bound=ReadSchema)
35
+ UpdateSchemaType = TypeVar('UpdateSchemaType', bound=UpdateSchema)
@@ -0,0 +1,52 @@
1
+ """
2
+ Пакет, содержащий репозиторий настроек.
3
+
4
+ Представлено 2 реализации:
5
+ - Env
6
+ - Prefect
7
+ """
8
+
9
+ from typing import Protocol, Self
10
+
11
+ from .enums import SettingsSourceEnum
12
+ from .env import EnvSettingsRepository
13
+ from .exceptions import SettingsRepositoryError as SettingsRepositoryError
14
+ from .type_vars import SettingsSchema
15
+
16
+
17
+ class SettingsRepositoryProtocol(Protocol):
18
+ """
19
+ Протокол репозитория настроек.
20
+ """
21
+
22
+ async def get(self: Self, schema_type: type[SettingsSchema], *, name: str | None = None) -> SettingsSchema:
23
+ """
24
+ Получаем настройки.
25
+ """
26
+ ...
27
+
28
+
29
+ class SettingsRepositoryFactoryProtocol(Protocol):
30
+ """
31
+ Протокол фабрики репозиториев настроек.
32
+ """
33
+
34
+ async def make(self: Self, settings_source: SettingsSourceEnum) -> SettingsRepositoryProtocol:
35
+ """
36
+ Создаем репозиторий настроек.
37
+ """
38
+ ...
39
+
40
+
41
+ class SettingsRepositoryFactoryImpl:
42
+ """
43
+ Реализация фабрики репозиториев настроек.
44
+ """
45
+
46
+ async def make(self: Self, settings_source: SettingsSourceEnum) -> SettingsRepositoryProtocol:
47
+ """
48
+ Создаем репозиторий настроек.
49
+ """
50
+ match settings_source:
51
+ case SettingsSourceEnum.ENV:
52
+ return EnvSettingsRepository()
@@ -0,0 +1,16 @@
1
+ """
2
+ Модуль, содержащий перечисления репозитория настроек.
3
+ """
4
+
5
+ from enum import StrEnum, auto
6
+
7
+
8
+ class SettingsSourceEnum(StrEnum):
9
+ """
10
+ Источник настроек.
11
+ """
12
+
13
+ ENV = auto()
14
+ """
15
+ Настройки, получаемые из переменных окружения.
16
+ """
@@ -0,0 +1,55 @@
1
+ """
2
+ Модуль, содержащий репозиторий настроек, получаемых из переменных окружения.
3
+ """
4
+
5
+ from typing import Self
6
+
7
+ from fast_clean.settings import BaseSettingsSchema, CoreSettingsSchema
8
+
9
+ from .exceptions import SettingsRepositoryError
10
+ from .type_vars import SettingsSchema
11
+
12
+
13
+ class EnvSettingsRepository:
14
+ """
15
+ Репозиторий настроек, получаемых из переменных окружения.
16
+ """
17
+
18
+ SETTINGS_MODULE = 'settings'
19
+
20
+ settings: list[BaseSettingsSchema] | None = None
21
+
22
+ async def get(self: Self, schema_type: type[SettingsSchema], *, name: str | None = None) -> SettingsSchema:
23
+ """
24
+ Получаем настройки из переменных окружения.
25
+ """
26
+ if self.settings is None:
27
+ self.settings = [st() for st in BaseSettingsSchema.descendant_types if st is not CoreSettingsSchema]
28
+ if name is not None:
29
+ return self.get_by_name(schema_type, name)
30
+ return self.get_by_type(schema_type)
31
+
32
+ def get_by_name(self: Self, schema_type: type[SettingsSchema], name: str) -> SettingsSchema:
33
+ """
34
+ Получаем настройки по имени.
35
+ """
36
+ assert self.settings
37
+ for settings in self.settings:
38
+ value = getattr(settings, name, None)
39
+ if value is not None and isinstance(value, schema_type):
40
+ return value
41
+ raise SettingsRepositoryError(f'Settings with name {name} not found')
42
+
43
+ def get_by_type(self: Self, schema_type: type[SettingsSchema]) -> SettingsSchema:
44
+ """
45
+ Получаем настройки по типу.
46
+ """
47
+ assert self.settings
48
+ for settings in self.settings:
49
+ if isinstance(settings, schema_type):
50
+ return settings
51
+ for key in settings.__dict__.keys():
52
+ value = getattr(settings, key, None)
53
+ if isinstance(value, schema_type):
54
+ return value
55
+ raise SettingsRepositoryError(f'Settings with type {schema_type} not found')
@@ -0,0 +1,13 @@
1
+ """
2
+ Модуль, содержащий исключения репозитория настроек.
3
+ """
4
+
5
+
6
+ class SettingsRepositoryError(Exception):
7
+ """
8
+ Ошибка репозитория настроек.
9
+ """
10
+
11
+ def __init__(self, message: str, *args: object) -> None:
12
+ super().__init__(*args)
13
+ self.message = message
@@ -0,0 +1,9 @@
1
+ """
2
+ Модуль, содержащий переменные типов.
3
+ """
4
+
5
+ from typing import TypeVar
6
+
7
+ from pydantic import BaseModel
8
+
9
+ SettingsSchema = TypeVar('SettingsSchema', bound=BaseModel)
@@ -0,0 +1,114 @@
1
+ """
2
+ Пакет, содержащий репозиторий файлового хранилища.
3
+
4
+ Представлено 2 реализации:
5
+ - Local
6
+ - S3
7
+ """
8
+
9
+ from pathlib import Path
10
+ from typing import AsyncContextManager, Protocol, Self
11
+
12
+ from .enums import StorageTypeEnum
13
+ from .local import LocalStorageRepository
14
+ from .reader import StreamReaderProtocol, StreamReadProtocol
15
+ from .s3 import S3StorageRepository
16
+ from .schemas import (
17
+ LocalStorageParamsSchema,
18
+ S3StorageParamsSchema,
19
+ StorageParamsSchema,
20
+ )
21
+
22
+
23
+ class StorageRepositoryProtocol(Protocol):
24
+ """
25
+ Протокол репозитория файлового хранилища.
26
+ """
27
+
28
+ async def exists(self: Self, path: str | Path) -> bool:
29
+ """
30
+ Проверяем существует ли файл.
31
+ """
32
+ ...
33
+
34
+ async def listdir(self: Self, path: str | Path) -> list[str]:
35
+ """
36
+ Получаем список файлов и директорий в заданной директории.
37
+ """
38
+ ...
39
+
40
+ async def is_file(self: Self, path: str | Path) -> bool:
41
+ """
42
+ Проверяем находится ли файл по пути.
43
+ """
44
+ ...
45
+
46
+ async def is_dir(self: Self, path: str | Path) -> bool:
47
+ """
48
+ Проверяем находится ли директория по пути.
49
+ """
50
+ ...
51
+
52
+ async def read(self: Self, path: str | Path) -> bytes:
53
+ """
54
+ Читаем содержимое файла.
55
+ """
56
+ ...
57
+
58
+ def stream_read(self: Self, path: str | Path) -> AsyncContextManager[StreamReaderProtocol]:
59
+ """
60
+ Читаем содержимое файла в потоковом режиме.
61
+ """
62
+ ...
63
+
64
+ async def write(self: Self, path: str | Path, content: str | bytes) -> None:
65
+ """
66
+ Создаем файл или переписываем существующий.
67
+ """
68
+ ...
69
+
70
+ async def stream_write(
71
+ self: Self,
72
+ path: str | Path,
73
+ stream: StreamReadProtocol,
74
+ length: int = -1,
75
+ part_size: int = 0,
76
+ ) -> None:
77
+ """
78
+ Создаем файл или переписываем существующий в потоковом режиме.
79
+ """
80
+ ...
81
+
82
+ async def delete(self: Self, path: str | Path) -> None:
83
+ """
84
+ Удаляем файл.
85
+ """
86
+ ...
87
+
88
+
89
+ class StorageRepositoryFactoryProtocol(Protocol):
90
+ """
91
+ Протокол фабрики репозиториев файлового хранилища.
92
+ """
93
+
94
+ async def make(self, storage_type: StorageTypeEnum, params: StorageParamsSchema) -> StorageRepositoryProtocol:
95
+ """
96
+ Создаем репозиторий файлового хранилища.
97
+ """
98
+ ...
99
+
100
+
101
+ class StorageRepositoryFactoryImpl:
102
+ """
103
+ Реализация фабрики репозиториев файлового хранилища.
104
+ """
105
+
106
+ async def make(self: Self, storage_type: StorageTypeEnum, params: StorageParamsSchema) -> StorageRepositoryProtocol:
107
+ """
108
+ Создаем репозиторий файлового хранилища.
109
+ """
110
+ if storage_type == StorageTypeEnum.S3 and isinstance(params, S3StorageParamsSchema):
111
+ return S3StorageRepository(params)
112
+ elif storage_type == StorageTypeEnum.LOCAL and isinstance(params, LocalStorageParamsSchema):
113
+ return LocalStorageRepository(params)
114
+ raise NotImplementedError()
@@ -0,0 +1,20 @@
1
+ """
2
+ Модуль, содержащий перечисления файлового хранилища.
3
+ """
4
+
5
+ from enum import StrEnum, auto
6
+
7
+
8
+ class StorageTypeEnum(StrEnum):
9
+ """
10
+ Тип хранилища.
11
+ """
12
+
13
+ S3 = auto()
14
+ """
15
+ Хранилище S3.
16
+ """
17
+ LOCAL = auto()
18
+ """
19
+ Локальное файловое хранилище.
20
+ """