fast-clean 1.0.0__tar.gz → 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. {fast_clean-1.0.0 → fast_clean-1.1.0}/PKG-INFO +1 -1
  2. fast_clean-1.1.0/fast_clean/container.py +100 -0
  3. fast_clean-1.1.0/fast_clean/depends.py +213 -0
  4. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/cache/__init__.py +6 -6
  5. fast_clean-1.1.0/fast_clean/services/__init__.py +12 -0
  6. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/services/cryptography/__init__.py +5 -17
  7. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/services/lock.py +5 -5
  8. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/services/seed.py +3 -15
  9. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/services/transaction.py +3 -15
  10. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/tools/cryptography.py +10 -14
  11. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/tools/load_seed.py +3 -3
  12. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean.egg-info/PKG-INFO +1 -1
  13. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean.egg-info/SOURCES.txt +0 -1
  14. {fast_clean-1.0.0 → fast_clean-1.1.0}/pyproject.toml +4 -2
  15. fast_clean-1.0.0/fast_clean/container.py +0 -235
  16. fast_clean-1.0.0/fast_clean/depends.py +0 -255
  17. fast_clean-1.0.0/fast_clean/services/__init__.py +0 -16
  18. fast_clean-1.0.0/tests/test_container.py +0 -126
  19. {fast_clean-1.0.0 → fast_clean-1.1.0}/README.md +0 -0
  20. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/__init__.py +0 -0
  21. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/broker.py +0 -0
  22. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/contrib/__init__.py +0 -0
  23. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/contrib/healthcheck/__init__.py +0 -0
  24. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/contrib/healthcheck/router.py +0 -0
  25. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/db.py +0 -0
  26. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/enums.py +0 -0
  27. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/exceptions.py +0 -0
  28. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/loggers.py +0 -0
  29. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/middleware.py +0 -0
  30. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/models.py +0 -0
  31. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/py.typed +0 -0
  32. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/redis.py +0 -0
  33. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/__init__.py +0 -0
  34. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/cache/in_memory.py +0 -0
  35. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/cache/redis.py +0 -0
  36. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/crud/__init__.py +0 -0
  37. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/crud/db.py +0 -0
  38. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/crud/in_memory.py +0 -0
  39. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/crud/type_vars.py +0 -0
  40. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/settings/__init__.py +0 -0
  41. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/settings/enums.py +0 -0
  42. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/settings/env.py +0 -0
  43. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/settings/exceptions.py +0 -0
  44. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/settings/type_vars.py +0 -0
  45. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/storage/__init__.py +0 -0
  46. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/storage/enums.py +0 -0
  47. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/storage/local.py +0 -0
  48. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/storage/reader.py +0 -0
  49. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/storage/s3.py +0 -0
  50. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/repositories/storage/schemas.py +0 -0
  51. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/schemas/__init__.py +0 -0
  52. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/schemas/exceptions.py +0 -0
  53. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/schemas/pagination.py +0 -0
  54. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/schemas/repository.py +0 -0
  55. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/schemas/request_response.py +0 -0
  56. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/schemas/status_response.py +0 -0
  57. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/services/cryptography/aes.py +0 -0
  58. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/services/cryptography/enums.py +0 -0
  59. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/settings.py +0 -0
  60. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/tools/__init__.py +0 -0
  61. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/use_cases.py +0 -0
  62. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/utils/__init__.py +0 -0
  63. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/utils/process.py +0 -0
  64. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/utils/pydantic.py +0 -0
  65. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/utils/ssl_context.py +0 -0
  66. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/utils/string.py +0 -0
  67. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/utils/thread.py +0 -0
  68. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/utils/time.py +0 -0
  69. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/utils/type_converters.py +0 -0
  70. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean/utils/typer.py +0 -0
  71. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean.egg-info/dependency_links.txt +0 -0
  72. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean.egg-info/requires.txt +0 -0
  73. {fast_clean-1.0.0 → fast_clean-1.1.0}/fast_clean.egg-info/top_level.txt +0 -0
  74. {fast_clean-1.0.0 → fast_clean-1.1.0}/setup.cfg +0 -0
  75. {fast_clean-1.0.0 → fast_clean-1.1.0}/tests/test_broker.py +0 -0
  76. {fast_clean-1.0.0 → fast_clean-1.1.0}/tests/test_db.py +0 -0
  77. {fast_clean-1.0.0 → fast_clean-1.1.0}/tests/test_exceptions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fast-clean
3
- Version: 1.0.0
3
+ Version: 1.1.0
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
@@ -0,0 +1,100 @@
1
+ """
2
+ Модуль, содержащий контейнер зависимостей.
3
+ """
4
+
5
+ import importlib
6
+ import os
7
+ import sys
8
+ from collections.abc import AsyncIterator
9
+ from contextlib import asynccontextmanager
10
+ from pathlib import Path
11
+
12
+ from dishka import AsyncContainer, Provider, make_async_container
13
+ from dishka.integrations.fastapi import FastapiProvider, setup_dishka
14
+ from fastapi import FastAPI
15
+
16
+
17
+ class ContainerManager:
18
+ """
19
+ Менеджер для управления контейнером зависимостей.
20
+ """
21
+
22
+ DEPENDS_MODULE = 'depends'
23
+
24
+ container: AsyncContainer | None = None
25
+
26
+ @classmethod
27
+ def init(cls, module_names: set[str] | None = None) -> AsyncContainer:
28
+ """
29
+ Инициализируем контейнер зависимостей.
30
+ """
31
+ if cls.container is None:
32
+ cls.container = cls.create(module_names)
33
+ return cls.container
34
+
35
+ @classmethod
36
+ def init_for_fastapi(cls, app: FastAPI, module_names: set[str] | None = None) -> AsyncContainer:
37
+ """
38
+ Инициализируем контейнер зависимостей для приложения FastAPI.
39
+ """
40
+ container = cls.init(module_names)
41
+ setup_dishka(container=container, app=app)
42
+ return container
43
+
44
+ @classmethod
45
+ async def close(cls) -> None:
46
+ """
47
+ Закрываем контейнер зависимостей.
48
+ """
49
+ if cls.container is None:
50
+ return
51
+ await cls.container.close()
52
+ cls.container = None
53
+
54
+ @classmethod
55
+ def create(cls, module_names: set[str] | None = None) -> AsyncContainer:
56
+ """
57
+ Создаем контейнер зависимостей.
58
+ """
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))
62
+
63
+ @classmethod
64
+ def get_default_module_names(cls) -> set[str]:
65
+ """
66
+ Получаем список модулей с зависимостями по умолчанию.
67
+ """
68
+ cwd = Path(os.getcwd())
69
+ virtual_env_paths = {path.parent for path in cwd.rglob('pyvenv.cfg')}
70
+ module_names: set[str] = set()
71
+ for path in cwd.rglob(f'{cls.DEPENDS_MODULE}.py'):
72
+ if not any(path.is_relative_to(venv) for venv in virtual_env_paths):
73
+ module_names.add('.'.join(str(path.relative_to(cwd).with_suffix('')).split('/')))
74
+ module_names.add(f'fast_clean.{cls.DEPENDS_MODULE}')
75
+ return module_names
76
+
77
+ @staticmethod
78
+ def get_providers(module_names: set[str]) -> list[Provider]:
79
+ """
80
+ Получаем провайдеры зависимостей.
81
+ """
82
+ providers: list[Provider] = []
83
+ for module_name in module_names:
84
+ module = sys.modules[module_name] if module_name in sys.modules else importlib.import_module(module_name)
85
+ for obj in module.__dict__.values():
86
+ if isinstance(obj, Provider):
87
+ providers.append(obj)
88
+ return providers
89
+
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
@@ -0,0 +1,213 @@
1
+ """
2
+ Модуль, содержащий зависимости.
3
+ """
4
+
5
+ import json
6
+ from collections.abc import AsyncIterator, Sequence
7
+ from typing import Annotated
8
+
9
+ from dishka import Provider, Scope, provide
10
+ from fastapi import Depends, Request
11
+ from faststream.kafka import KafkaBroker
12
+ from flatten_dict import unflatten
13
+ from sqlalchemy.ext.asyncio import AsyncSession
14
+ from starlette.datastructures import FormData
15
+ from stringcase import snakecase
16
+
17
+ from .broker import BrokerFactory
18
+ from .db import SessionFactory, SessionManagerImpl, SessionManagerProtocol
19
+ from .redis import RedisManager
20
+ from .repositories import (
21
+ CacheManager,
22
+ CacheRepositoryProtocol,
23
+ LocalStorageParamsSchema,
24
+ S3StorageParamsSchema,
25
+ SettingsRepositoryFactoryImpl,
26
+ SettingsRepositoryFactoryProtocol,
27
+ SettingsRepositoryProtocol,
28
+ SettingsSourceEnum,
29
+ StorageRepositoryFactoryImpl,
30
+ StorageRepositoryFactoryProtocol,
31
+ StorageRepositoryProtocol,
32
+ StorageTypeEnum,
33
+ )
34
+ from .schemas import PaginationRequestSchema
35
+ from .services import (
36
+ CryptographicAlgorithmEnum,
37
+ CryptographyServiceFactory,
38
+ CryptographyServiceProtocol,
39
+ LockServiceProtocol,
40
+ RedisLockService,
41
+ SeedService,
42
+ TransactionService,
43
+ )
44
+ from .settings import CoreCacheSettingsSchema, CoreKafkaSettingsSchema, CoreSettingsSchema, CoreStorageSettingsSchema
45
+
46
+
47
+ async def get_nested_form_data(request: Request) -> FormData:
48
+ """
49
+ Получаем форму, позволяющую использовать вложенные словари.
50
+ """
51
+ dot_data = {k.replace('[', '.').replace(']', ''): v for k, v in (await request.form()).items()}
52
+ nested_data = unflatten(dot_data, 'dot')
53
+ for k, v in nested_data.items():
54
+ if isinstance(v, dict):
55
+ nested_data[k] = json.dumps(v)
56
+ return FormData(nested_data)
57
+
58
+
59
+ def get_pagination(page: int | None = None, page_size: int | None = None) -> PaginationRequestSchema:
60
+ """
61
+ Получаем входные данные пагинации.
62
+ """
63
+ return PaginationRequestSchema(page=page or 1, page_size=page_size or 10)
64
+
65
+
66
+ def get_sorting(sorting: str | None = None) -> Sequence[str]:
67
+ """
68
+ Получаем входные данные сортировки.
69
+ """
70
+ if not sorting:
71
+ return []
72
+ return [s[0] + snakecase(s[1:]) if s[0] == '-' else snakecase(s) for s in sorting.split(',')]
73
+
74
+
75
+ NestedFormData = Annotated[FormData, Depends(get_nested_form_data)]
76
+ Pagination = Annotated[PaginationRequestSchema, Depends(get_pagination)]
77
+ Sorting = Annotated[Sequence[str], Depends(get_sorting)]
78
+
79
+
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
208
+ RedisManager.init(settings.redis_dsn)
209
+ assert RedisManager.redis is not None
210
+ return RedisLockService(RedisManager.redis)
211
+
212
+
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)
@@ -0,0 +1,12 @@
1
+ """
2
+ Пакет, содержащий сервисы.
3
+ """
4
+
5
+ from .cryptography import AesGcmCryptographyService as AesGcmCryptographyService
6
+ from .cryptography import CryptographicAlgorithmEnum as CryptographicAlgorithmEnum
7
+ from .cryptography import CryptographyServiceFactory as CryptographyServiceFactory
8
+ from .cryptography import CryptographyServiceProtocol as CryptographyServiceProtocol
9
+ from .lock import LockServiceProtocol as LockServiceProtocol
10
+ from .lock import RedisLockService as RedisLockService
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.0
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
@@ -67,6 +67,5 @@ fast_clean/utils/time.py
67
67
  fast_clean/utils/type_converters.py
68
68
  fast_clean/utils/typer.py
69
69
  tests/test_broker.py
70
- tests/test_container.py
71
70
  tests/test_db.py
72
71
  tests/test_exceptions.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fast-clean"
3
- version = "1.0.0"
3
+ version = "1.1.0"
4
4
  description = "FastAPI Clean Architecture implementation"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -44,6 +44,8 @@ dev = [
44
44
  "ruff>=0.9.7",
45
45
  ]
46
46
 
47
+ [tool.ruff.format]
48
+ quote-style = "single"
47
49
 
48
50
  [tool.black]
49
51
  skip-string-normalization = true
@@ -103,4 +105,4 @@ version_toml = [
103
105
  commit_message = "chore(release): {version} [skip ci]"
104
106
  branch = "main"
105
107
  upload_to_pypi = false
106
- upload_to_release = true
108
+ upload_to_release = true