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.
- fast_clean/__init__.py +3 -0
- fast_clean/broker.py +123 -0
- fast_clean/container.py +235 -0
- fast_clean/contrib/__init__.py +3 -0
- fast_clean/contrib/healthcheck/__init__.py +3 -0
- fast_clean/contrib/healthcheck/router.py +17 -0
- fast_clean/db.py +179 -0
- fast_clean/depends.py +255 -0
- fast_clean/enums.py +39 -0
- fast_clean/exceptions.py +281 -0
- fast_clean/loggers.py +26 -0
- fast_clean/middleware.py +20 -0
- fast_clean/models.py +33 -0
- fast_clean/py.typed +0 -0
- fast_clean/redis.py +23 -0
- fast_clean/repositories/__init__.py +30 -0
- fast_clean/repositories/cache/__init__.py +83 -0
- fast_clean/repositories/cache/in_memory.py +62 -0
- fast_clean/repositories/cache/redis.py +58 -0
- fast_clean/repositories/crud/__init__.py +149 -0
- fast_clean/repositories/crud/db.py +559 -0
- fast_clean/repositories/crud/in_memory.py +369 -0
- fast_clean/repositories/crud/type_vars.py +35 -0
- fast_clean/repositories/settings/__init__.py +52 -0
- fast_clean/repositories/settings/enums.py +16 -0
- fast_clean/repositories/settings/env.py +55 -0
- fast_clean/repositories/settings/exceptions.py +13 -0
- fast_clean/repositories/settings/type_vars.py +9 -0
- fast_clean/repositories/storage/__init__.py +114 -0
- fast_clean/repositories/storage/enums.py +20 -0
- fast_clean/repositories/storage/local.py +118 -0
- fast_clean/repositories/storage/reader.py +122 -0
- fast_clean/repositories/storage/s3.py +118 -0
- fast_clean/repositories/storage/schemas.py +31 -0
- fast_clean/schemas/__init__.py +25 -0
- fast_clean/schemas/exceptions.py +32 -0
- fast_clean/schemas/pagination.py +65 -0
- fast_clean/schemas/repository.py +43 -0
- fast_clean/schemas/request_response.py +36 -0
- fast_clean/schemas/status_response.py +13 -0
- fast_clean/services/__init__.py +16 -0
- fast_clean/services/cryptography/__init__.py +57 -0
- fast_clean/services/cryptography/aes.py +120 -0
- fast_clean/services/cryptography/enums.py +20 -0
- fast_clean/services/lock.py +57 -0
- fast_clean/services/seed.py +91 -0
- fast_clean/services/transaction.py +40 -0
- fast_clean/settings.py +189 -0
- fast_clean/tools/__init__.py +6 -0
- fast_clean/tools/cryptography.py +56 -0
- fast_clean/tools/load_seed.py +31 -0
- fast_clean/use_cases.py +38 -0
- fast_clean/utils/__init__.py +15 -0
- fast_clean/utils/process.py +31 -0
- fast_clean/utils/pydantic.py +23 -0
- fast_clean/utils/ssl_context.py +31 -0
- fast_clean/utils/string.py +28 -0
- fast_clean/utils/thread.py +21 -0
- fast_clean/utils/time.py +14 -0
- fast_clean/utils/type_converters.py +18 -0
- fast_clean/utils/typer.py +23 -0
- fast_clean-0.4.0.dist-info/METADATA +38 -0
- fast_clean-0.4.0.dist-info/RECORD +65 -0
- fast_clean-0.4.0.dist-info/WHEEL +5 -0
- 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,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
|
+
"""
|