fast-clean 1.0.0__py3-none-any.whl → 1.1.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/container.py +50 -185
- fast_clean/depends.py +135 -177
- fast_clean/repositories/cache/__init__.py +6 -6
- fast_clean/services/__init__.py +3 -7
- fast_clean/services/cryptography/__init__.py +5 -17
- fast_clean/services/lock.py +5 -5
- fast_clean/services/seed.py +3 -15
- fast_clean/services/transaction.py +3 -15
- fast_clean/tools/cryptography.py +10 -14
- fast_clean/tools/load_seed.py +3 -3
- {fast_clean-1.0.0.dist-info → fast_clean-1.1.0.dist-info}/METADATA +1 -1
- {fast_clean-1.0.0.dist-info → fast_clean-1.1.0.dist-info}/RECORD +14 -14
- {fast_clean-1.0.0.dist-info → fast_clean-1.1.0.dist-info}/WHEEL +0 -0
- {fast_clean-1.0.0.dist-info → fast_clean-1.1.0.dist-info}/top_level.txt +0 -0
fast_clean/container.py
CHANGED
@@ -2,196 +2,63 @@
|
|
2
2
|
Модуль, содержащий контейнер зависимостей.
|
3
3
|
"""
|
4
4
|
|
5
|
-
from __future__ import annotations
|
6
|
-
|
7
5
|
import importlib
|
8
|
-
import inspect
|
9
6
|
import os
|
10
7
|
import sys
|
11
|
-
from collections.abc import
|
12
|
-
from contextlib import
|
8
|
+
from collections.abc import AsyncIterator
|
9
|
+
from contextlib import asynccontextmanager
|
13
10
|
from pathlib import Path
|
14
|
-
from types import TracebackType
|
15
|
-
from typing import (
|
16
|
-
Annotated,
|
17
|
-
Any,
|
18
|
-
Protocol,
|
19
|
-
Self,
|
20
|
-
get_args,
|
21
|
-
get_origin,
|
22
|
-
get_type_hints,
|
23
|
-
)
|
24
|
-
|
25
|
-
from fastapi.dependencies.utils import (
|
26
|
-
is_async_gen_callable,
|
27
|
-
is_coroutine_callable,
|
28
|
-
is_gen_callable,
|
29
|
-
solve_generator,
|
30
|
-
)
|
31
|
-
from fastapi.params import Depends
|
32
|
-
from starlette.concurrency import run_in_threadpool
|
33
|
-
from stringcase import pascalcase
|
34
|
-
|
35
|
-
from .exceptions import ContainerError
|
36
|
-
|
37
|
-
|
38
|
-
class ContainerProtocol(Protocol):
|
39
|
-
"""
|
40
|
-
Протокол контейнера зависимостей.
|
41
|
-
"""
|
42
|
-
|
43
|
-
def __call__(self: Self, extra: dict[tuple[str, Any], Any] | None = None) -> ContainerProtocol:
|
44
|
-
"""
|
45
|
-
Получаем новый контейнер.
|
46
|
-
"""
|
47
|
-
...
|
48
|
-
|
49
|
-
async def __aenter__(self: Self) -> ContainerProtocol:
|
50
|
-
"""
|
51
|
-
Открываем асинхронный контекстный менеджер для управления стеком контекстных менеджеров.
|
52
|
-
"""
|
53
|
-
...
|
54
11
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
exc_val: BaseException | None = None,
|
59
|
-
exc_tb: TracebackType | None = None,
|
60
|
-
) -> None:
|
61
|
-
"""
|
62
|
-
Закрываем асинхронный контекстный менеджер для управления стеком контекстных менеджеров.
|
63
|
-
"""
|
64
|
-
...
|
12
|
+
from dishka import AsyncContainer, Provider, make_async_container
|
13
|
+
from dishka.integrations.fastapi import FastapiProvider, setup_dishka
|
14
|
+
from fastapi import FastAPI
|
65
15
|
|
66
|
-
async def get_by_type(self: Self, dependency_type: Any, *, extra: dict[tuple[str, Any], Any] | None = None) -> Any:
|
67
|
-
"""
|
68
|
-
Получаем зависимость по типу.
|
69
|
-
"""
|
70
|
-
...
|
71
16
|
|
72
|
-
|
73
|
-
"""
|
74
|
-
Получаем зависимость по имени.
|
75
|
-
"""
|
76
|
-
...
|
77
|
-
|
78
|
-
|
79
|
-
class ContainerImpl:
|
17
|
+
class ContainerManager:
|
80
18
|
"""
|
81
|
-
|
19
|
+
Менеджер для управления контейнером зависимостей.
|
82
20
|
"""
|
83
21
|
|
84
22
|
DEPENDS_MODULE = 'depends'
|
85
23
|
|
86
|
-
|
87
|
-
dependencies_type_mapping: dict[type, Annotated[Any, Depends]] | None = None
|
88
|
-
|
89
|
-
def __init__(self, extra: dict[tuple[str, Any], Any] | None = None) -> None:
|
90
|
-
self.extra = extra
|
91
|
-
self.instances: dict[type, Any] = {}
|
92
|
-
self.async_exit_stack: AsyncExitStack | None = None
|
24
|
+
container: AsyncContainer | None = None
|
93
25
|
|
94
|
-
|
95
|
-
|
96
|
-
Получаем новый контейнер.
|
97
|
-
"""
|
98
|
-
return ContainerImpl(extra)
|
99
|
-
|
100
|
-
async def __aenter__(self: Self) -> ContainerProtocol:
|
101
|
-
"""
|
102
|
-
Открываем асинхронный контекстный менеджер для управления стеком контекстных менеджеров.
|
103
|
-
"""
|
104
|
-
self.async_exit_stack = AsyncExitStack()
|
105
|
-
return self
|
106
|
-
|
107
|
-
async def __aexit__(
|
108
|
-
self: Self,
|
109
|
-
exc_type: type[BaseException] | None = None,
|
110
|
-
exc_val: BaseException | None = None,
|
111
|
-
exc_tb: TracebackType | None = None,
|
112
|
-
) -> None:
|
113
|
-
"""
|
114
|
-
Закрываем асинхронный контекстный менеджер для управления стеком контекстных менеджеров.
|
115
|
-
"""
|
116
|
-
assert self.async_exit_stack is not None
|
117
|
-
await self.async_exit_stack.__aexit__(exc_type, exc_val, exc_tb)
|
118
|
-
|
119
|
-
async def get_by_type(self: Self, dependency_type: Any, *, extra: dict[tuple[str, Any], Any] | None = None) -> Any:
|
120
|
-
"""
|
121
|
-
Получаем зависимость по типу.
|
122
|
-
"""
|
123
|
-
extra = extra or self.extra or {}
|
124
|
-
assert self.dependencies_type_mapping is not None
|
125
|
-
if dependency_type not in self.dependencies_type_mapping:
|
126
|
-
raise ContainerError(f'Dependency {dependency_type} not found')
|
127
|
-
return await self.resolve_dependency(self.dependencies_type_mapping[dependency_type], extra)
|
128
|
-
|
129
|
-
async def get_by_name(self: Self, name: str, *, extra: dict[tuple[str, Any], Any] | None = None) -> Any:
|
26
|
+
@classmethod
|
27
|
+
def init(cls, module_names: set[str] | None = None) -> AsyncContainer:
|
130
28
|
"""
|
131
|
-
|
29
|
+
Инициализируем контейнер зависимостей.
|
132
30
|
"""
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
if name not in self.dependencies_name_mapping:
|
137
|
-
raise ContainerError(f'Dependency {name} not found')
|
138
|
-
return await self.resolve_dependency(self.dependencies_name_mapping[name], extra)
|
31
|
+
if cls.container is None:
|
32
|
+
cls.container = cls.create(module_names)
|
33
|
+
return cls.container
|
139
34
|
|
140
|
-
|
35
|
+
@classmethod
|
36
|
+
def init_for_fastapi(cls, app: FastAPI, module_names: set[str] | None = None) -> AsyncContainer:
|
141
37
|
"""
|
142
|
-
|
38
|
+
Инициализируем контейнер зависимостей для приложения FastAPI.
|
143
39
|
"""
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
if dependency_type in self.instances:
|
148
|
-
return self.instances[dependency_type]
|
149
|
-
call = depends.dependency
|
150
|
-
assert call is not None
|
151
|
-
resolved_params = await self.resolve_params(call, extra)
|
152
|
-
if is_gen_callable(call) or is_async_gen_callable(call):
|
153
|
-
if self.async_exit_stack is None:
|
154
|
-
raise ContainerError('Generator requires `async_exit_stack`')
|
155
|
-
resolved = await solve_generator(call=call, stack=self.async_exit_stack, sub_values=resolved_params)
|
156
|
-
elif is_coroutine_callable(call):
|
157
|
-
resolved = await call(**resolved_params)
|
158
|
-
else:
|
159
|
-
resolved = await run_in_threadpool(call, **resolved_params)
|
160
|
-
if depends.use_cache:
|
161
|
-
self.instances[dependency_type] = resolved
|
162
|
-
return resolved
|
40
|
+
container = cls.init(module_names)
|
41
|
+
setup_dishka(container=container, app=app)
|
42
|
+
return container
|
163
43
|
|
164
|
-
|
44
|
+
@classmethod
|
45
|
+
async def close(cls) -> None:
|
165
46
|
"""
|
166
|
-
|
47
|
+
Закрываем контейнер зависимостей.
|
167
48
|
"""
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
annotation = type_hints[param_name]
|
173
|
-
args = get_args(annotation)
|
174
|
-
if (param_name, annotation) in extra:
|
175
|
-
extra_value = extra[param_name, annotation]
|
176
|
-
resolved_params[param_name] = extra_value() if callable(extra_value) else extra_value
|
177
|
-
elif get_origin(annotation) is Annotated and len(args) == 2 and isinstance(args[1], Depends):
|
178
|
-
resolved_params[param_name] = await self.resolve_dependency(annotation, extra)
|
179
|
-
elif param.default is not inspect.Parameter.empty:
|
180
|
-
resolved_params[param_name] = param.default
|
181
|
-
else:
|
182
|
-
raise ContainerError(f'Can not resolve dependency {param_name}: {annotation}')
|
183
|
-
return resolved_params
|
49
|
+
if cls.container is None:
|
50
|
+
return
|
51
|
+
await cls.container.close()
|
52
|
+
cls.container = None
|
184
53
|
|
185
54
|
@classmethod
|
186
|
-
def
|
55
|
+
def create(cls, module_names: set[str] | None = None) -> AsyncContainer:
|
187
56
|
"""
|
188
|
-
|
57
|
+
Создаем контейнер зависимостей.
|
189
58
|
"""
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
if cls.dependencies_type_mapping is None:
|
194
|
-
cls.dependencies_type_mapping = cls.get_dependencies_type_mapping()
|
59
|
+
module_names = module_names or set()
|
60
|
+
module_names.update(cls.get_default_module_names())
|
61
|
+
return make_async_container(FastapiProvider(), *cls.get_providers(module_names))
|
195
62
|
|
196
63
|
@classmethod
|
197
64
|
def get_default_module_names(cls) -> set[str]:
|
@@ -208,28 +75,26 @@ class ContainerImpl:
|
|
208
75
|
return module_names
|
209
76
|
|
210
77
|
@staticmethod
|
211
|
-
def
|
212
|
-
module_names: set[str],
|
213
|
-
) -> dict[str, Annotated[Any, Depends]]:
|
78
|
+
def get_providers(module_names: set[str]) -> list[Provider]:
|
214
79
|
"""
|
215
|
-
Получаем
|
80
|
+
Получаем провайдеры зависимостей.
|
216
81
|
"""
|
217
|
-
|
82
|
+
providers: list[Provider] = []
|
218
83
|
for module_name in module_names:
|
219
84
|
module = sys.modules[module_name] if module_name in sys.modules else importlib.import_module(module_name)
|
220
|
-
for
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
return name_mapping
|
85
|
+
for obj in module.__dict__.values():
|
86
|
+
if isinstance(obj, Provider):
|
87
|
+
providers.append(obj)
|
88
|
+
return providers
|
225
89
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
90
|
+
|
91
|
+
@asynccontextmanager
|
92
|
+
async def get_container() -> AsyncIterator[AsyncContainer]:
|
93
|
+
"""
|
94
|
+
Получаем контейнер зависимостей.
|
95
|
+
"""
|
96
|
+
container = ContainerManager.container
|
97
|
+
if container is None:
|
98
|
+
container = ContainerManager.init()
|
99
|
+
async with container() as nested_container:
|
100
|
+
yield nested_container
|
fast_clean/depends.py
CHANGED
@@ -6,6 +6,7 @@ import json
|
|
6
6
|
from collections.abc import AsyncIterator, Sequence
|
7
7
|
from typing import Annotated
|
8
8
|
|
9
|
+
from dishka import Provider, Scope, provide
|
9
10
|
from fastapi import Depends, Request
|
10
11
|
from faststream.kafka import KafkaBroker
|
11
12
|
from flatten_dict import unflatten
|
@@ -14,7 +15,6 @@ from starlette.datastructures import FormData
|
|
14
15
|
from stringcase import snakecase
|
15
16
|
|
16
17
|
from .broker import BrokerFactory
|
17
|
-
from .container import ContainerImpl, ContainerProtocol
|
18
18
|
from .db import SessionFactory, SessionManagerImpl, SessionManagerProtocol
|
19
19
|
from .redis import RedisManager
|
20
20
|
from .repositories import (
|
@@ -34,28 +34,15 @@ from .repositories import (
|
|
34
34
|
from .schemas import PaginationRequestSchema
|
35
35
|
from .services import (
|
36
36
|
CryptographicAlgorithmEnum,
|
37
|
-
|
38
|
-
CryptographyServiceFactoryProtocol,
|
37
|
+
CryptographyServiceFactory,
|
39
38
|
CryptographyServiceProtocol,
|
40
39
|
LockServiceProtocol,
|
41
40
|
RedisLockService,
|
42
|
-
|
43
|
-
|
44
|
-
TransactionServiceImpl,
|
45
|
-
TransactionServiceProtocol,
|
41
|
+
SeedService,
|
42
|
+
TransactionService,
|
46
43
|
)
|
47
44
|
from .settings import CoreCacheSettingsSchema, CoreKafkaSettingsSchema, CoreSettingsSchema, CoreStorageSettingsSchema
|
48
45
|
|
49
|
-
# --- utils ---
|
50
|
-
|
51
|
-
|
52
|
-
def get_container() -> ContainerProtocol:
|
53
|
-
"""
|
54
|
-
Получаем контейнер зависимостей.
|
55
|
-
"""
|
56
|
-
ContainerImpl.init()
|
57
|
-
return ContainerImpl()
|
58
|
-
|
59
46
|
|
60
47
|
async def get_nested_form_data(request: Request) -> FormData:
|
61
48
|
"""
|
@@ -85,171 +72,142 @@ def get_sorting(sorting: str | None = None) -> Sequence[str]:
|
|
85
72
|
return [s[0] + snakecase(s[1:]) if s[0] == '-' else snakecase(s) for s in sorting.split(',')]
|
86
73
|
|
87
74
|
|
88
|
-
Container = Annotated[ContainerProtocol, Depends(get_container)]
|
89
75
|
NestedFormData = Annotated[FormData, Depends(get_nested_form_data)]
|
90
76
|
Pagination = Annotated[PaginationRequestSchema, Depends(get_pagination)]
|
91
77
|
Sorting = Annotated[Sequence[str], Depends(get_sorting)]
|
92
78
|
|
93
|
-
# --- repositories ---
|
94
|
-
|
95
|
-
|
96
|
-
def get_settings_repository_factory() -> SettingsRepositoryFactoryProtocol:
|
97
|
-
"""
|
98
|
-
Получаем фабрику репозиториев настроек.
|
99
|
-
"""
|
100
|
-
return SettingsRepositoryFactoryImpl()
|
101
|
-
|
102
79
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
80
|
+
class CoreProvider(Provider):
|
81
|
+
"""
|
82
|
+
Провайдер зависимостей.
|
83
|
+
"""
|
84
|
+
|
85
|
+
scope = Scope.REQUEST
|
86
|
+
|
87
|
+
# --- repositories ---
|
88
|
+
|
89
|
+
settings_repository_factory = provide(
|
90
|
+
SettingsRepositoryFactoryImpl, provides=SettingsRepositoryFactoryProtocol, scope=Scope.APP
|
91
|
+
)
|
92
|
+
storage_repository_factory = provide(
|
93
|
+
StorageRepositoryFactoryImpl, provides=StorageRepositoryFactoryProtocol, scope=Scope.APP
|
94
|
+
)
|
95
|
+
|
96
|
+
@provide(scope=Scope.APP)
|
97
|
+
@staticmethod
|
98
|
+
async def get_settings_repository(
|
99
|
+
settings_repository_factory: SettingsRepositoryFactoryProtocol,
|
100
|
+
) -> SettingsRepositoryProtocol:
|
101
|
+
"""
|
102
|
+
Получаем репозиторий настроек.
|
103
|
+
"""
|
104
|
+
return await settings_repository_factory.make(SettingsSourceEnum.ENV)
|
105
|
+
|
106
|
+
@provide(scope=Scope.APP)
|
107
|
+
@staticmethod
|
108
|
+
async def get_settings(settings_repository: SettingsRepositoryProtocol) -> CoreSettingsSchema:
|
109
|
+
"""
|
110
|
+
Получаем настройки.
|
111
|
+
"""
|
112
|
+
return await settings_repository.get(CoreSettingsSchema)
|
113
|
+
|
114
|
+
@staticmethod
|
115
|
+
async def get_broker_repository(settings_repository: SettingsRepositoryProtocol) -> AsyncIterator[KafkaBroker]:
|
116
|
+
"""
|
117
|
+
Получаем репозиторий брокера сообщений.
|
118
|
+
"""
|
119
|
+
kafka_settings = await settings_repository.get(CoreKafkaSettingsSchema)
|
120
|
+
yield BrokerFactory.make_static(kafka_settings)
|
121
|
+
|
122
|
+
@provide(scope=Scope.APP)
|
123
|
+
@staticmethod
|
124
|
+
async def get_cache_repository(settings_repository: SettingsRepositoryProtocol) -> CacheRepositoryProtocol:
|
125
|
+
"""
|
126
|
+
Получаем репозиторий кеша.
|
127
|
+
"""
|
128
|
+
settings = await settings_repository.get(CoreSettingsSchema)
|
129
|
+
if settings.redis_dsn is not None:
|
130
|
+
RedisManager.init(settings.redis_dsn)
|
131
|
+
cache_settings = await settings_repository.get(CoreCacheSettingsSchema)
|
132
|
+
if CacheManager.cache_repository is None:
|
133
|
+
CacheManager.init(cache_settings, RedisManager.redis)
|
134
|
+
if CacheManager.cache_repository is not None:
|
135
|
+
return CacheManager.cache_repository
|
136
|
+
raise ValueError('Cache is not initialized')
|
137
|
+
|
138
|
+
@provide(scope=Scope.APP)
|
139
|
+
@staticmethod
|
140
|
+
async def get_storage_repository(
|
141
|
+
settings_repository: SettingsRepositoryProtocol,
|
142
|
+
storage_repository_factory: StorageRepositoryFactoryProtocol,
|
143
|
+
) -> StorageRepositoryProtocol:
|
144
|
+
"""
|
145
|
+
Получаем репозиторий файлового хранилища.
|
146
|
+
"""
|
147
|
+
storage_settings = await settings_repository.get(CoreStorageSettingsSchema)
|
148
|
+
if storage_settings.provider == 's3' and storage_settings.s3 is not None:
|
149
|
+
return await storage_repository_factory.make(
|
150
|
+
StorageTypeEnum.S3,
|
151
|
+
S3StorageParamsSchema.model_validate(storage_settings.s3.model_dump()),
|
152
|
+
)
|
153
|
+
elif storage_settings.provider == 'local' and storage_settings.dir is not None:
|
154
|
+
return await storage_repository_factory.make(
|
155
|
+
StorageTypeEnum.LOCAL, LocalStorageParamsSchema(path=storage_settings.dir)
|
156
|
+
)
|
157
|
+
raise NotImplementedError(f'Storage {storage_settings.provider} not allowed')
|
158
|
+
|
159
|
+
# --- db ---
|
160
|
+
|
161
|
+
@provide
|
162
|
+
@staticmethod
|
163
|
+
async def get_async_session(settings_repository: SettingsRepositoryProtocol) -> AsyncIterator[AsyncSession]:
|
164
|
+
"""
|
165
|
+
Получаем асинхронную сессию.
|
166
|
+
"""
|
167
|
+
async with SessionFactory.make_async_session_static(settings_repository) as session:
|
168
|
+
yield session
|
169
|
+
|
170
|
+
@provide
|
171
|
+
@staticmethod
|
172
|
+
def get_session_manager(session: AsyncSession) -> SessionManagerProtocol:
|
173
|
+
"""
|
174
|
+
Получаем менеджер сессий.
|
175
|
+
"""
|
176
|
+
return SessionManagerImpl(session)
|
177
|
+
|
178
|
+
# --- services ---
|
179
|
+
|
180
|
+
seed_service = provide(SeedService)
|
181
|
+
transaction_service = provide(TransactionService)
|
182
|
+
|
183
|
+
@provide(scope=Scope.APP)
|
184
|
+
@staticmethod
|
185
|
+
def get_cryptography_service_factory(settings: CoreSettingsSchema) -> CryptographyServiceFactory:
|
186
|
+
"""
|
187
|
+
Получаем фабрику сервисов криптографии.
|
188
|
+
"""
|
189
|
+
return CryptographyServiceFactory(settings.secret_key)
|
190
|
+
|
191
|
+
@provide(scope=Scope.APP)
|
192
|
+
@staticmethod
|
193
|
+
async def get_cryptography_service(
|
194
|
+
cryptography_service_factory: CryptographyServiceFactory,
|
195
|
+
) -> CryptographyServiceProtocol:
|
196
|
+
"""
|
197
|
+
Получаем сервис криптографии.
|
198
|
+
"""
|
199
|
+
return await cryptography_service_factory.make(CryptographicAlgorithmEnum.AES_GCM)
|
200
|
+
|
201
|
+
@provide(scope=Scope.APP)
|
202
|
+
@staticmethod
|
203
|
+
def get_lock_service(settings: CoreSettingsSchema) -> LockServiceProtocol:
|
204
|
+
"""
|
205
|
+
Получаем сервис распределенной блокировки.
|
206
|
+
"""
|
207
|
+
assert settings.redis_dsn is not None
|
140
208
|
RedisManager.init(settings.redis_dsn)
|
141
|
-
|
142
|
-
|
143
|
-
CacheManager.init(cache_settings, RedisManager.redis)
|
144
|
-
if CacheManager.cache is not None:
|
145
|
-
return CacheManager.cache
|
146
|
-
raise ValueError('Cache is not initialized')
|
147
|
-
|
148
|
-
|
149
|
-
def get_storage_repository_factory() -> StorageRepositoryFactoryProtocol:
|
150
|
-
"""
|
151
|
-
Получаем фабрику репозиториев файлового хранилища.
|
152
|
-
"""
|
153
|
-
return StorageRepositoryFactoryImpl()
|
154
|
-
|
155
|
-
|
156
|
-
BrokerRepository = Annotated[KafkaBroker, Depends(get_broker_repository)]
|
157
|
-
CacheRepository = Annotated[CacheRepositoryProtocol, Depends(get_cache_repository)]
|
158
|
-
StorageRepositoryFactory = Annotated[StorageRepositoryFactoryProtocol, Depends(get_storage_repository_factory)]
|
159
|
-
|
160
|
-
|
161
|
-
async def get_storage_repository(
|
162
|
-
settings_repository: SettingsRepository,
|
163
|
-
storage_repository_factory: StorageRepositoryFactory,
|
164
|
-
) -> StorageRepositoryProtocol:
|
165
|
-
"""
|
166
|
-
Получаем репозиторий файлового хранилища.
|
167
|
-
"""
|
168
|
-
storage_settings = await settings_repository.get(CoreStorageSettingsSchema)
|
169
|
-
if storage_settings.provider == 's3' and storage_settings.s3 is not None:
|
170
|
-
return await storage_repository_factory.make(
|
171
|
-
StorageTypeEnum.S3,
|
172
|
-
S3StorageParamsSchema.model_validate(storage_settings.s3.model_dump()),
|
173
|
-
)
|
174
|
-
elif storage_settings.provider == 'local':
|
175
|
-
return await storage_repository_factory.make(
|
176
|
-
StorageTypeEnum.LOCAL, LocalStorageParamsSchema(path=storage_settings.dir)
|
177
|
-
)
|
178
|
-
raise NotImplementedError(f'Storage {storage_settings.provider} not allowed')
|
179
|
-
|
180
|
-
|
181
|
-
StorageRepository = Annotated[StorageRepositoryProtocol, Depends(get_storage_repository)]
|
182
|
-
|
183
|
-
# --- db ---
|
184
|
-
|
185
|
-
|
186
|
-
async def get_async_session(settings_repository: SettingsRepository) -> AsyncIterator[AsyncSession]:
|
187
|
-
"""
|
188
|
-
Получаем асинхронную сессию.
|
189
|
-
"""
|
190
|
-
async with SessionFactory.make_async_session_static(settings_repository) as session:
|
191
|
-
yield session
|
192
|
-
|
193
|
-
|
194
|
-
Session = Annotated[AsyncSession, Depends(get_async_session)]
|
195
|
-
|
196
|
-
|
197
|
-
def get_session_manager(session: Session) -> SessionManagerProtocol:
|
198
|
-
"""
|
199
|
-
Получаем менеджер сессий.
|
200
|
-
"""
|
201
|
-
return SessionManagerImpl(session)
|
202
|
-
|
203
|
-
|
204
|
-
SessionManager = Annotated[SessionManagerProtocol, Depends(get_session_manager)]
|
205
|
-
|
206
|
-
# --- services ---
|
207
|
-
|
208
|
-
|
209
|
-
def get_cryptography_service_factory(settings: Settings) -> CryptographyServiceFactoryProtocol:
|
210
|
-
"""
|
211
|
-
Получаем фабрику сервисов криптографии.
|
212
|
-
"""
|
213
|
-
return CryptographyServiceFactoryImpl(settings.secret_key)
|
214
|
-
|
215
|
-
|
216
|
-
CryptographyServiceFactory = Annotated[CryptographyServiceFactoryProtocol, Depends(get_cryptography_service_factory)]
|
217
|
-
|
218
|
-
|
219
|
-
async def get_cryptography_service(
|
220
|
-
cryptography_service_factory: CryptographyServiceFactory,
|
221
|
-
) -> CryptographyServiceProtocol:
|
222
|
-
"""
|
223
|
-
Получаем сервис криптографии.
|
224
|
-
"""
|
225
|
-
return await cryptography_service_factory.make(CryptographicAlgorithmEnum.AES_GCM)
|
226
|
-
|
227
|
-
|
228
|
-
def get_lock_service(settings: Settings) -> LockServiceProtocol:
|
229
|
-
"""
|
230
|
-
Получаем сервис распределенной блокировки.
|
231
|
-
"""
|
232
|
-
assert settings.redis_dsn is not None
|
233
|
-
RedisManager.init(settings.redis_dsn)
|
234
|
-
assert RedisManager.redis is not None
|
235
|
-
return RedisLockService(RedisManager.redis)
|
236
|
-
|
237
|
-
|
238
|
-
def get_seed_service(session_manager: SessionManager) -> SeedServiceProtocol:
|
239
|
-
"""
|
240
|
-
Получаем сервис для загрузки данных из файлов.
|
241
|
-
"""
|
242
|
-
return SeedServiceImpl(session_manager)
|
243
|
-
|
244
|
-
|
245
|
-
def get_transaction_service(session: Session) -> TransactionServiceProtocol:
|
246
|
-
"""
|
247
|
-
Получаем сервис транзакций.
|
248
|
-
"""
|
249
|
-
return TransactionServiceImpl(session)
|
209
|
+
assert RedisManager.redis is not None
|
210
|
+
return RedisLockService(RedisManager.redis)
|
250
211
|
|
251
212
|
|
252
|
-
|
253
|
-
LockService = Annotated[LockServiceProtocol, Depends(get_lock_service)]
|
254
|
-
SeedService = Annotated[SeedServiceProtocol, Depends(get_seed_service)]
|
255
|
-
TransactionService = Annotated[TransactionServiceProtocol, Depends(get_transaction_service)]
|
213
|
+
provider = CoreProvider()
|
@@ -6,7 +6,7 @@
|
|
6
6
|
- Redis
|
7
7
|
"""
|
8
8
|
|
9
|
-
from typing import ClassVar, Protocol, Self,
|
9
|
+
from typing import ClassVar, Protocol, Self, cast
|
10
10
|
|
11
11
|
from fastapi_cache import FastAPICache
|
12
12
|
|
@@ -34,7 +34,7 @@ class CacheRepositoryProtocol(Protocol):
|
|
34
34
|
"""
|
35
35
|
...
|
36
36
|
|
37
|
-
async def get_with_ttl(self: Self, key: str) ->
|
37
|
+
async def get_with_ttl(self: Self, key: str) -> tuple[int, str | None]:
|
38
38
|
"""
|
39
39
|
Получаем значение со сроком жизни.
|
40
40
|
"""
|
@@ -61,17 +61,17 @@ class CacheRepositoryProtocol(Protocol):
|
|
61
61
|
|
62
62
|
class CacheManager:
|
63
63
|
"""
|
64
|
-
Менеджер для
|
64
|
+
Менеджер для работы с репозиторием кеша.
|
65
65
|
"""
|
66
66
|
|
67
|
-
|
67
|
+
cache_repository: ClassVar[CacheRepositoryProtocol | None] = None
|
68
68
|
|
69
69
|
@classmethod
|
70
70
|
def init(cls, cache_settings: CoreCacheSettingsSchema, redis: aioredis.Redis | None) -> None:
|
71
71
|
"""
|
72
72
|
Инициализируем кеш.
|
73
73
|
"""
|
74
|
-
if cls.
|
74
|
+
if cls.cache_repository is None:
|
75
75
|
cache_backend: InMemoryCacheRepository | RedisCacheRepository
|
76
76
|
match cache_settings.provider:
|
77
77
|
case 'in_memory':
|
@@ -80,4 +80,4 @@ class CacheManager:
|
|
80
80
|
assert redis is not None
|
81
81
|
cache_backend = RedisCacheRepository(redis)
|
82
82
|
FastAPICache.init(cache_backend, prefix=cache_settings.prefix)
|
83
|
-
cls.
|
83
|
+
cls.cache_repository = cast(CacheRepositoryProtocol, cache_backend)
|
fast_clean/services/__init__.py
CHANGED
@@ -2,15 +2,11 @@
|
|
2
2
|
Пакет, содержащий сервисы.
|
3
3
|
"""
|
4
4
|
|
5
|
-
from .cryptography import AesCbcCryptographyService as AesCbcCryptographyService
|
6
5
|
from .cryptography import AesGcmCryptographyService as AesGcmCryptographyService
|
7
6
|
from .cryptography import CryptographicAlgorithmEnum as CryptographicAlgorithmEnum
|
8
|
-
from .cryptography import
|
9
|
-
from .cryptography import CryptographyServiceFactoryProtocol as CryptographyServiceFactoryProtocol
|
7
|
+
from .cryptography import CryptographyServiceFactory as CryptographyServiceFactory
|
10
8
|
from .cryptography import CryptographyServiceProtocol as CryptographyServiceProtocol
|
11
9
|
from .lock import LockServiceProtocol as LockServiceProtocol
|
12
10
|
from .lock import RedisLockService as RedisLockService
|
13
|
-
from .seed import
|
14
|
-
from .
|
15
|
-
from .transaction import TransactionServiceImpl as TransactionServiceImpl
|
16
|
-
from .transaction import TransactionServiceProtocol as TransactionServiceProtocol
|
11
|
+
from .seed import SeedService as SeedService
|
12
|
+
from .transaction import TransactionService as TransactionService
|
@@ -4,7 +4,8 @@
|
|
4
4
|
|
5
5
|
from typing import Protocol, Self
|
6
6
|
|
7
|
-
from .aes import AesCbcCryptographyService
|
7
|
+
from .aes import AesCbcCryptographyService as AesCbcCryptographyService
|
8
|
+
from .aes import AesGcmCryptographyService
|
8
9
|
from .enums import CryptographicAlgorithmEnum
|
9
10
|
|
10
11
|
|
@@ -26,21 +27,9 @@ class CryptographyServiceProtocol(Protocol):
|
|
26
27
|
...
|
27
28
|
|
28
29
|
|
29
|
-
class
|
30
|
+
class CryptographyServiceFactory:
|
30
31
|
"""
|
31
|
-
|
32
|
-
"""
|
33
|
-
|
34
|
-
async def make(self: Self, algorithm: CryptographicAlgorithmEnum) -> CryptographyServiceProtocol:
|
35
|
-
"""
|
36
|
-
Создаем сервис криптографии для шифрования секретных параметров.
|
37
|
-
"""
|
38
|
-
...
|
39
|
-
|
40
|
-
|
41
|
-
class CryptographyServiceFactoryImpl:
|
42
|
-
"""
|
43
|
-
Реализация фабрики сервисов криптографии для шифрования секретных параметров.
|
32
|
+
Фабрика сервисов криптографии для шифрования секретных параметров.
|
44
33
|
"""
|
45
34
|
|
46
35
|
def __init__(self, secret_key: str) -> None:
|
@@ -53,5 +42,4 @@ class CryptographyServiceFactoryImpl:
|
|
53
42
|
match algorithm:
|
54
43
|
case CryptographicAlgorithmEnum.AES_GCM:
|
55
44
|
return AesGcmCryptographyService(self.secret_key)
|
56
|
-
|
57
|
-
return AesCbcCryptographyService(self.secret_key)
|
45
|
+
raise NotImplementedError(algorithm)
|
fast_clean/services/lock.py
CHANGED
@@ -4,11 +4,11 @@
|
|
4
4
|
|
5
5
|
from collections.abc import AsyncIterator
|
6
6
|
from contextlib import asynccontextmanager
|
7
|
-
from typing import AsyncContextManager, Protocol
|
7
|
+
from typing import AsyncContextManager, Protocol
|
8
8
|
|
9
|
+
import redis.asyncio.lock as aioredis_lock
|
9
10
|
from fast_clean.exceptions import LockError
|
10
11
|
from redis import asyncio as aioredis
|
11
|
-
from redis.exceptions import LockError as RedisLockError
|
12
12
|
|
13
13
|
|
14
14
|
class LockServiceProtocol(Protocol):
|
@@ -17,7 +17,7 @@ class LockServiceProtocol(Protocol):
|
|
17
17
|
"""
|
18
18
|
|
19
19
|
def lock(
|
20
|
-
self
|
20
|
+
self,
|
21
21
|
name: str,
|
22
22
|
*,
|
23
23
|
timeout: float | None = None,
|
@@ -40,7 +40,7 @@ class RedisLockService:
|
|
40
40
|
|
41
41
|
@asynccontextmanager
|
42
42
|
async def lock(
|
43
|
-
self
|
43
|
+
self,
|
44
44
|
name: str,
|
45
45
|
*,
|
46
46
|
timeout: float | None = None,
|
@@ -53,5 +53,5 @@ class RedisLockService:
|
|
53
53
|
try:
|
54
54
|
async with self.redis.lock(name, timeout=timeout, sleep=sleep, blocking_timeout=blocking_timeout):
|
55
55
|
yield
|
56
|
-
except
|
56
|
+
except aioredis_lock.LockError as lock_error:
|
57
57
|
raise LockError() from lock_error
|
fast_clean/services/seed.py
CHANGED
@@ -6,7 +6,7 @@ import importlib
|
|
6
6
|
import json
|
7
7
|
import os
|
8
8
|
from pathlib import Path
|
9
|
-
from typing import Any,
|
9
|
+
from typing import Any, cast
|
10
10
|
|
11
11
|
import sqlalchemy as sa
|
12
12
|
from sqlalchemy.dialects.postgresql import insert
|
@@ -15,19 +15,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|
15
15
|
from ..db import SessionManagerProtocol
|
16
16
|
|
17
17
|
|
18
|
-
class
|
19
|
-
"""
|
20
|
-
Протокол сервиса для загрузки данных из файлов.
|
21
|
-
"""
|
22
|
-
|
23
|
-
async def load_data(self: Self, directory: str | Path | None = None) -> None:
|
24
|
-
"""
|
25
|
-
Загружаем данные из файлов по пути.
|
26
|
-
"""
|
27
|
-
...
|
28
|
-
|
29
|
-
|
30
|
-
class SeedServiceImpl:
|
18
|
+
class SeedService:
|
31
19
|
"""
|
32
20
|
Реализация сервиса для загрузки данных из файлов.
|
33
21
|
"""
|
@@ -35,7 +23,7 @@ class SeedServiceImpl:
|
|
35
23
|
def __init__(self, session_manager: SessionManagerProtocol) -> None:
|
36
24
|
self.session_manager = session_manager
|
37
25
|
|
38
|
-
async def load_data(self
|
26
|
+
async def load_data(self, directory: str | Path | None = None) -> None:
|
39
27
|
"""
|
40
28
|
Загружаем данные из файлов по пути.
|
41
29
|
"""
|
@@ -3,25 +3,13 @@
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
from contextlib import asynccontextmanager
|
6
|
-
from typing import
|
6
|
+
from typing import AsyncIterator
|
7
7
|
|
8
8
|
import sqlalchemy as sa
|
9
9
|
from sqlalchemy.ext.asyncio import AsyncSession
|
10
10
|
|
11
11
|
|
12
|
-
class
|
13
|
-
"""
|
14
|
-
Протокол сервиса транзакций.
|
15
|
-
"""
|
16
|
-
|
17
|
-
def begin(self: Self, immediate: bool = True) -> AsyncContextManager[None]:
|
18
|
-
"""
|
19
|
-
Начинаем транзакцию.
|
20
|
-
"""
|
21
|
-
...
|
22
|
-
|
23
|
-
|
24
|
-
class TransactionServiceImpl:
|
12
|
+
class TransactionService:
|
25
13
|
"""
|
26
14
|
Реализация сервиса транзакций.
|
27
15
|
"""
|
@@ -30,7 +18,7 @@ class TransactionServiceImpl:
|
|
30
18
|
self.session = session
|
31
19
|
|
32
20
|
@asynccontextmanager
|
33
|
-
async def begin(self
|
21
|
+
async def begin(self, immediate: bool = True) -> AsyncIterator[None]:
|
34
22
|
"""
|
35
23
|
Начинаем транзакцию.
|
36
24
|
"""
|
fast_clean/tools/cryptography.py
CHANGED
@@ -6,8 +6,8 @@ from typing import Annotated
|
|
6
6
|
|
7
7
|
import typer
|
8
8
|
|
9
|
-
from fast_clean.
|
10
|
-
from fast_clean.services import CryptographicAlgorithmEnum,
|
9
|
+
from fast_clean.container import get_container
|
10
|
+
from fast_clean.services import CryptographicAlgorithmEnum, CryptographyServiceFactory
|
11
11
|
from fast_clean.utils import typer_async
|
12
12
|
|
13
13
|
|
@@ -21,12 +21,10 @@ async def encrypt(
|
|
21
21
|
"""
|
22
22
|
Зашифровываем данные.
|
23
23
|
"""
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
cryptography_service = await cryptography_service_factory.make(algorithm)
|
29
|
-
print(cryptography_service.encrypt(data))
|
24
|
+
async with get_container() as container:
|
25
|
+
cryptography_service_factory = await container.get(CryptographyServiceFactory)
|
26
|
+
cryptography_service = await cryptography_service_factory.make(algorithm)
|
27
|
+
print(cryptography_service.encrypt(data))
|
30
28
|
|
31
29
|
|
32
30
|
@typer_async
|
@@ -39,12 +37,10 @@ async def decrypt(
|
|
39
37
|
"""
|
40
38
|
Расшифровываем данные.
|
41
39
|
"""
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
cryptography_service = await cryptography_service_factory.make(algorithm)
|
47
|
-
print(cryptography_service.decrypt(data))
|
40
|
+
async with get_container() as container:
|
41
|
+
cryptography_service_factory = await container.get(CryptographyServiceFactory)
|
42
|
+
cryptography_service = await cryptography_service_factory.make(algorithm)
|
43
|
+
print(cryptography_service.decrypt(data))
|
48
44
|
|
49
45
|
|
50
46
|
def use_cryptography(app: typer.Typer) -> None:
|
fast_clean/tools/load_seed.py
CHANGED
@@ -6,8 +6,8 @@ from typing import Annotated
|
|
6
6
|
|
7
7
|
import typer
|
8
8
|
|
9
|
-
from fast_clean.
|
10
|
-
from fast_clean.services import
|
9
|
+
from fast_clean.container import get_container
|
10
|
+
from fast_clean.services import SeedService
|
11
11
|
from fast_clean.utils import typer_async
|
12
12
|
|
13
13
|
|
@@ -19,7 +19,7 @@ async def load_seed(
|
|
19
19
|
Загружаем данные из файлов.
|
20
20
|
"""
|
21
21
|
async with get_container() as container:
|
22
|
-
seed_service
|
22
|
+
seed_service = await container.get(SeedService)
|
23
23
|
await seed_service.load_data(path)
|
24
24
|
|
25
25
|
|
@@ -1,8 +1,8 @@
|
|
1
1
|
fast_clean/__init__.py,sha256=sT4tb75t5PXws8W_7wpA0jNtNxkWPFLAMrPlDGS7RHw,51
|
2
2
|
fast_clean/broker.py,sha256=CHnL4Jd6jF5gKgtUXi33j9QFG2EUM4uqhVqdLuxIrZs,4474
|
3
|
-
fast_clean/container.py,sha256=
|
3
|
+
fast_clean/container.py,sha256=E1e0H1JqGOacH4uBNwkjTDXYhzN56yZi0AmWXQ3DkEQ,3535
|
4
4
|
fast_clean/db.py,sha256=d03D9cYHpq8iQ7ErAwZYggLhITmxD5feSr071kv0_x0,5507
|
5
|
-
fast_clean/depends.py,sha256=
|
5
|
+
fast_clean/depends.py,sha256=nSsn1-c7A0VmtudCI70z5wi0b7TmSUqvHjyZpTE3_wk,7750
|
6
6
|
fast_clean/enums.py,sha256=lPhC_2_r6YFby7Mq-9u_JSiuyZ0e57F2VxBfUwnBZ18,826
|
7
7
|
fast_clean/exceptions.py,sha256=Sp-k-a5z1Gedu0slzj1-rORnr4GP1FXDHKCKRaJq-7o,9485
|
8
8
|
fast_clean/loggers.py,sha256=hVvZSDMMxYnK-p_yyjd4R7SyHpmxQF3eKQEeMu9Q-jo,705
|
@@ -16,7 +16,7 @@ fast_clean/contrib/__init__.py,sha256=AcFNyhc0QGsOnYvzQGanDN3QIAsKpn4d8RIj73F-sG
|
|
16
16
|
fast_clean/contrib/healthcheck/__init__.py,sha256=p8hUCLdv2qGngTwAeTGIV4h_ZGDm9ZNWMrA5_k3Yi0E,106
|
17
17
|
fast_clean/contrib/healthcheck/router.py,sha256=6kyFuNqR5m3pB_fzerrZ7m7yvoqL_BiwkUMeLrxJVnE,408
|
18
18
|
fast_clean/repositories/__init__.py,sha256=IpETRNot2t6rI5qUtsyqinkloS2fhcqSVpB5-s1iUmY,1753
|
19
|
-
fast_clean/repositories/cache/__init__.py,sha256=
|
19
|
+
fast_clean/repositories/cache/__init__.py,sha256=pD7qIS6H8DrnhOptJiXrlGcWYUCIU3VmVQCLccyxx4Q,2511
|
20
20
|
fast_clean/repositories/cache/in_memory.py,sha256=Hb68UrTmQozALcyLrmYPBIfJfi67NvsCTDe1RfqwBHQ,2259
|
21
21
|
fast_clean/repositories/cache/redis.py,sha256=UjrA2CXQtMfHTpowz6Ot952y73YjTEr6zJlBbWblaws,1908
|
22
22
|
fast_clean/repositories/crud/__init__.py,sha256=CrfjDlf0QLQXzX4GO7VkDf_qaZMPl1Pz9pXv9fCyWSU,4391
|
@@ -40,16 +40,16 @@ fast_clean/schemas/pagination.py,sha256=GEQ-Tbhx6xkMMXhDNWrTEhPv8IdnAOJxH2P1tscm
|
|
40
40
|
fast_clean/schemas/repository.py,sha256=up4-c7irCRm73Xsq0jMu5pot1xMDOuNRNNopId0-Zn8,889
|
41
41
|
fast_clean/schemas/request_response.py,sha256=i4HTpjelWl4DxJ1sQaeanTWB_PThlhVJRhtMMGqRAiQ,693
|
42
42
|
fast_clean/schemas/status_response.py,sha256=mASZRCNtKJnDbmhr8_pBytkE_3NguyTIFqO4aw-nNEQ,269
|
43
|
-
fast_clean/services/__init__.py,sha256=
|
44
|
-
fast_clean/services/lock.py,sha256=
|
45
|
-
fast_clean/services/seed.py,sha256=
|
46
|
-
fast_clean/services/transaction.py,sha256=
|
47
|
-
fast_clean/services/cryptography/__init__.py,sha256=
|
43
|
+
fast_clean/services/__init__.py,sha256=Lvdb5ZibRGwoMn_WENrk9wERUViTsPrU8E_71XtPFJc,617
|
44
|
+
fast_clean/services/lock.py,sha256=Sw8LKAQ6w-EFSOeIOVsbB89lZa3LvF4ROiRpJFF5hzE,1642
|
45
|
+
fast_clean/services/seed.py,sha256=M0yA2I5z-jLM2UcW_x7287mwIFW5Vt0fPFaplakGFc0,2836
|
46
|
+
fast_clean/services/transaction.py,sha256=djXR6e6ukgpBXDbVmU095MvRJAIqdOPMgAcege52Qxg,762
|
47
|
+
fast_clean/services/cryptography/__init__.py,sha256=IDokTE-dJriQxdsov3yoVw1OrsgyxJLeBFJwi_Mb7iE,1531
|
48
48
|
fast_clean/services/cryptography/aes.py,sha256=_k0WtnKDaEKdUBegfwmqerE75ER44307CEQ-I2W0abo,4616
|
49
49
|
fast_clean/services/cryptography/enums.py,sha256=cLibSGv6LNVTUI2rm3_DtDwU68GYIAf4kY3GGbtnw1A,494
|
50
50
|
fast_clean/tools/__init__.py,sha256=m8n09uN47JGtAfgWVbXCJOxpzlrUazogqtLo6xPWe3s,181
|
51
|
-
fast_clean/tools/cryptography.py,sha256=
|
52
|
-
fast_clean/tools/load_seed.py,sha256=
|
51
|
+
fast_clean/tools/cryptography.py,sha256=Q79-Qb4-nyrid3CNr7uWplT6tghnivFI-bqNUTvqlIQ,1913
|
52
|
+
fast_clean/tools/load_seed.py,sha256=Tm5_r_myrC5dl_WyC6Bx2WKFAkfLf-Pch4ZK6zWN2Qg,867
|
53
53
|
fast_clean/utils/__init__.py,sha256=Q3OiJNdWl51Vd_wSP7iuZQIq4_SjM1mYkqIWPaw94WU,709
|
54
54
|
fast_clean/utils/process.py,sha256=6k2E1q7d31Wq6G5BqJqrX5czimvJExeltk7uO7CxiSg,936
|
55
55
|
fast_clean/utils/pydantic.py,sha256=FtBkNsxdlrhrlEiIHu2wZwF-UR4THETV8mw-h_jevYg,871
|
@@ -59,7 +59,7 @@ fast_clean/utils/thread.py,sha256=ChEWBLupnSEMq4Wro_aiW0QvCLUKedKc0TQFMu7Zg4g,56
|
|
59
59
|
fast_clean/utils/time.py,sha256=nvavbtG4zR_gkrGSbsqKAsBdePxO3LuTeoISbFZIgn0,307
|
60
60
|
fast_clean/utils/type_converters.py,sha256=bMEJeoQB9Q6Qok1-ppn4Ii8ZpIkZwJbD2IzCydSStHw,523
|
61
61
|
fast_clean/utils/typer.py,sha256=1O7BsNGn68bBzNbj0-Ycfhv35WpLzwvYTKn510YNXQQ,663
|
62
|
-
fast_clean-1.
|
63
|
-
fast_clean-1.
|
64
|
-
fast_clean-1.
|
65
|
-
fast_clean-1.
|
62
|
+
fast_clean-1.1.0.dist-info/METADATA,sha256=TwQl3DYFi2AAzEmjriNSSK0MImCSlUEzIRlbBibnlfQ,1030
|
63
|
+
fast_clean-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
64
|
+
fast_clean-1.1.0.dist-info/top_level.txt,sha256=QfsGs-QLmPCZWWPFOukD0zhMnokH68FoO2KeObl6ZIA,11
|
65
|
+
fast_clean-1.1.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|