fast-clean 1.0.0__py3-none-any.whl → 1.1.1__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 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 Callable
12
- from contextlib import AsyncExitStack
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
- async def __aexit__(
56
- self: Self,
57
- exc_type: type[BaseException] | None = None,
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
- async def get_by_name(self: Self, name: str, *, extra: dict[tuple[str, Any], Any] | None = None) -> Any:
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
- dependencies_name_mapping: dict[str, Annotated[Any, Depends]] | None = None
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
- def __call__(self: Self, extra: dict[tuple[str, Any], Any] | None = None) -> ContainerProtocol:
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
- extra = extra or self.extra or {}
134
- assert self.dependencies_name_mapping is not None
135
- name = pascalcase(name)
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
- async def resolve_dependency(self: Self, annotated_type: type, extra: dict[tuple[str, Any], Any]) -> Any:
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
- dependency_type: type
145
- depends: Depends
146
- dependency_type, depends = get_args(annotated_type)
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
- async def resolve_params(self: Self, call: Callable[..., Any], extra: dict[tuple[str, Any], Any]) -> dict[str, Any]:
44
+ @classmethod
45
+ async def close(cls) -> None:
165
46
  """
166
- Разрешаем параметры зависимости.
47
+ Закрываем контейнер зависимостей.
167
48
  """
168
- signature = inspect.signature(call)
169
- resolved_params: dict[str, Any] = {}
170
- type_hints = get_type_hints(call, include_extras=True)
171
- for param_name, param in signature.parameters.items():
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 init(cls, module_names: set[str] | None = None) -> None:
55
+ def create(cls, module_names: set[str] | None = None) -> AsyncContainer:
187
56
  """
188
- Инициализируем контейнер.
57
+ Создаем контейнер зависимостей.
189
58
  """
190
- if cls.dependencies_name_mapping is None:
191
- module_names = module_names or cls.get_default_module_names()
192
- cls.dependencies_name_mapping = cls.get_dependencies_name_mapping(module_names)
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 get_dependencies_name_mapping(
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
- name_mapping: dict[str, Annotated[Any, Depends]] = {}
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 name, obj in module.__dict__.items():
221
- args = get_args(obj)
222
- if get_origin(obj) is Annotated and len(args) == 2 and isinstance(args[1], Depends):
223
- name_mapping[name] = obj
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
- @classmethod
227
- def get_dependencies_type_mapping(cls) -> dict[type, Annotated[Any, Depends]]:
228
- """
229
- Получаем маппинг типов на зависимости.
230
- """
231
- assert cls.dependencies_name_mapping is not None
232
- type_mapping: dict[type, Annotated[Any, Depends]] = {}
233
- for dependency in cls.dependencies_name_mapping.values():
234
- type_mapping[get_args(dependency)[0]] = dependency
235
- return type_mapping
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
- CryptographyServiceFactoryImpl,
38
- CryptographyServiceFactoryProtocol,
37
+ CryptographyServiceFactory,
39
38
  CryptographyServiceProtocol,
40
39
  LockServiceProtocol,
41
40
  RedisLockService,
42
- SeedServiceImpl,
43
- SeedServiceProtocol,
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
- SettingsRepositoryFactory = Annotated[SettingsRepositoryFactoryProtocol, Depends(get_settings_repository_factory)]
104
-
105
-
106
- async def get_settings_repository(settings_repository_factory: SettingsRepositoryFactory) -> SettingsRepositoryProtocol:
107
- """
108
- Получаем репозиторий настроек.
109
- """
110
- return await settings_repository_factory.make(SettingsSourceEnum.ENV)
111
-
112
-
113
- SettingsRepository = Annotated[SettingsRepositoryProtocol, Depends(get_settings_repository)]
114
-
115
-
116
- async def get_settings(settings_repository: SettingsRepository) -> CoreSettingsSchema:
117
- """
118
- Получаем настройки.
119
- """
120
- return await settings_repository.get(CoreSettingsSchema)
121
-
122
-
123
- Settings = Annotated[CoreSettingsSchema, Depends(get_settings)]
124
-
125
-
126
- async def get_broker_repository(settings_repository: SettingsRepository) -> AsyncIterator[KafkaBroker]:
127
- """
128
- Получаем репозиторий брокера сообщений.
129
- """
130
- kafka_settings = await settings_repository.get(CoreKafkaSettingsSchema)
131
- yield BrokerFactory.make_static(kafka_settings)
132
-
133
-
134
- async def get_cache_repository(settings_repository: SettingsRepository) -> CacheRepositoryProtocol:
135
- """
136
- Получаем репозиторий кеша.
137
- """
138
- settings = await settings_repository.get(CoreSettingsSchema)
139
- if settings.redis_dsn is not None:
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
- cache_settings = await settings_repository.get(CoreCacheSettingsSchema)
142
- if CacheManager.cache is None:
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
- CryptographyService = Annotated[CryptographyServiceProtocol, Depends(get_cryptography_service)]
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, Tuple, cast
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) -> Tuple[int, str | None]:
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
- cache: ClassVar[CacheRepositoryProtocol | None] = None
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.cache is None:
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.cache = cast(CacheRepositoryProtocol, cache_backend)
83
+ cls.cache_repository = cast(CacheRepositoryProtocol, cache_backend)
@@ -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 CryptographyServiceFactoryImpl as CryptographyServiceFactoryImpl
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 SeedServiceImpl as SeedServiceImpl
14
- from .seed import SeedServiceProtocol as SeedServiceProtocol
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, AesGcmCryptographyService
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 CryptographyServiceFactoryProtocol(Protocol):
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
- case CryptographicAlgorithmEnum.AES_CBC:
57
- return AesCbcCryptographyService(self.secret_key)
45
+ raise NotImplementedError(algorithm)
@@ -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, Self
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: 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: 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 RedisLockError as lock_error:
56
+ except aioredis_lock.LockError as lock_error:
57
57
  raise LockError() from lock_error
@@ -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, Protocol, Self, cast
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 SeedServiceProtocol(Protocol):
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: Self, directory: str | Path | None = None) -> None:
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 AsyncContextManager, AsyncIterator, Protocol, Self
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 TransactionServiceProtocol(Protocol):
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: Self, immediate: bool = True) -> AsyncIterator[None]:
21
+ async def begin(self, immediate: bool = True) -> AsyncIterator[None]:
34
22
  """
35
23
  Начинаем транзакцию.
36
24
  """
@@ -6,8 +6,8 @@ from typing import Annotated
6
6
 
7
7
  import typer
8
8
 
9
- from fast_clean.depends import get_container
10
- from fast_clean.services import CryptographicAlgorithmEnum, CryptographyServiceFactoryProtocol
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
- container = get_container()
25
- cryptography_service_factory: CryptographyServiceFactoryProtocol = await container.get_by_type(
26
- CryptographyServiceFactoryProtocol
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
- container = get_container()
43
- cryptography_service_factory: CryptographyServiceFactoryProtocol = await container.get_by_type(
44
- CryptographyServiceFactoryProtocol
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:
@@ -6,8 +6,8 @@ from typing import Annotated
6
6
 
7
7
  import typer
8
8
 
9
- from fast_clean.depends import get_container
10
- from fast_clean.services import SeedServiceProtocol
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: SeedServiceProtocol = await container.get_by_type(SeedServiceProtocol)
22
+ seed_service = await container.get(SeedService)
23
23
  await seed_service.load_data(path)
24
24
 
25
25
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fast-clean
3
- Version: 1.0.0
3
+ Version: 1.1.1
4
4
  Summary: FastAPI Clean Architecture implementation
5
5
  Author-email: Luferov Victor <luferovvs@yandex.ru>, Orlov Artem <squakrazv@yandex.ru>
6
6
  Requires-Python: >=3.12
@@ -8,6 +8,7 @@ Description-Content-Type: text/markdown
8
8
  Requires-Dist: aiofiles>=24.1.0
9
9
  Requires-Dist: aiokafka>=0.12.0
10
10
  Requires-Dist: cryptography>=44.0.1
11
+ Requires-Dist: dishka>=1.6.0
11
12
  Requires-Dist: fastapi>=0.115.8
12
13
  Requires-Dist: fastapi-cache2[redis]>=0.2.2
13
14
  Requires-Dist: faststream>=0.5.34
@@ -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=i7ZLZY_UN6ohGn-PT3RgAkzRPza3-DWRfgRdoaeMBWU,9562
3
+ fast_clean/container.py,sha256=E1e0H1JqGOacH4uBNwkjTDXYhzN56yZi0AmWXQ3DkEQ,3535
4
4
  fast_clean/db.py,sha256=d03D9cYHpq8iQ7ErAwZYggLhITmxD5feSr071kv0_x0,5507
5
- fast_clean/depends.py,sha256=94mIIfXoY7X_lm0xrcd15QjrGkyr_QLF0Om5byJNuJ0,8824
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=9i3_AbUDt4HbPBRUfvNb2HqYGTDhZ6ZRRqz9jKE5o9c,2479
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=hhT-tt4pQxD7EanesryzxPa9MqIavaU8bj4lrVGXLi0,964
44
- fast_clean/services/lock.py,sha256=sWrlEP4rWkKTJgcnR1UrBOnZ8kOvuji4aNWr9VPzjwo,1665
45
- fast_clean/services/seed.py,sha256=dytqnztOdvAoja3-bs_ldqf6LnoAUcaGEti2TxTTYOU,3201
46
- fast_clean/services/transaction.py,sha256=9XW084PnBgN-g9hBSSDs5NN6QbrsVArBjOLMuhn-W_8,1093
47
- fast_clean/services/cryptography/__init__.py,sha256=aXGBi49z9qeaRyTM7ZU9qncN-PfwlSXAJ_WSBuyYMf4,2071
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=NznbY5wtXeX8doyn6Hf76ztakckto5QnsUDK1GK7RWE,2001
52
- fast_clean/tools/load_seed.py,sha256=Bql-c-hjFdrf-qsGo5V7DSX9sa9_RCk_P9BLF7G5xts,910
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.0.0.dist-info/METADATA,sha256=jt-CLrTfxZI6T1S020P1uRyhTHD-o_hoT32hXqZx8Ro,1030
63
- fast_clean-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
64
- fast_clean-1.0.0.dist-info/top_level.txt,sha256=QfsGs-QLmPCZWWPFOukD0zhMnokH68FoO2KeObl6ZIA,11
65
- fast_clean-1.0.0.dist-info/RECORD,,
62
+ fast_clean-1.1.1.dist-info/METADATA,sha256=IxnSOCrX-M6DG4WknIO7XRoQ-N7rBQ5ZUdFcd5MBiZQ,1059
63
+ fast_clean-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
64
+ fast_clean-1.1.1.dist-info/top_level.txt,sha256=QfsGs-QLmPCZWWPFOukD0zhMnokH68FoO2KeObl6ZIA,11
65
+ fast_clean-1.1.1.dist-info/RECORD,,