Skip to content

Commit aa6ba1c

Browse files
lint+refactor containers
1 parent a843364 commit aa6ba1c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+343
-264
lines changed

app/application/repository/interfaces.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Record repository interface."""
22

33
from abc import ABC, abstractmethod
4+
from types import TracebackType
45
from typing import List, Self
56

67
from app.domain.entities.sample_record import SampleRecord
@@ -10,11 +11,17 @@ class IUnitOfWork(ABC):
1011
"""Interface for unit of work operations."""
1112

1213
@abstractmethod
13-
async def __aenter__(self):
14+
async def __aenter__(self) -> Self:
1415
return self
1516

1617
@abstractmethod
17-
async def __aexit__(self, exc_type, exc_val, exc_tb):
18+
@abstractmethod
19+
async def __aexit__(
20+
self,
21+
exc_type: type[BaseException] | None,
22+
exc_val: BaseException | None,
23+
exc_tb: TracebackType | None,
24+
) -> None:
1825
pass
1926

2027

@@ -81,4 +88,3 @@ async def get_by_id(self, record_id: int) -> SampleRecord:
8188
async def get_all(self) -> List[SampleRecord]:
8289
"""Get all records from the database"""
8390
pass
84-

app/application/use_cases/interfaces.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212

1313
class ISampleRecordUseCases(ABC):
14-
"""Interface for record use cases."""
14+
"""Interface for the sample record use cases."""
1515

1616
@abstractmethod
1717
async def create_record(

app/application/use_cases/record_use_cases.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313

1414
class SampleRecordUseCases(ISampleRecordUseCases):
15-
"""Implementation of samplr record use cases."""
15+
"""Implementation of sample record use cases."""
1616

1717
def __init__(self, record_repo: ISampleRecordRepository):
1818
self._repo = record_repo

app/decorators/mapper/context.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from functools import cached_property
2-
from typing import Callable, Any
2+
from typing import Any, Callable
33

44

55
class ExceptionContext:
6-
"""Class to store exception rising context."""
6+
"""Class to get exception rising context."""
7+
78
SENSITIVE_KEYS: frozenset[str] = frozenset(
89
("password", "token", "key", "secret", "auth", "credential", "passwd")
910
)
@@ -22,8 +23,7 @@ def __init__(
2223

2324
@cached_property
2425
def formatted_context(self) -> str:
25-
"""Format exception context for logging.
26-
"""
26+
"""Format exception context for logging."""
2727
error_context = [
2828
f"Error in function '{self.func.__module__}.{self.func.__qualname__}'"
2929
]

app/decorators/mapper/exception_mapper.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88

99
from app.decorators.mapper.context import ExceptionContext
1010
from app.decorators.mapper.factories import ExceptionFactory
11-
from app.logger import logger
1211

1312
ExceptionOrTupleOfExceptions = Type[Exception] | tuple[Type[Exception], ...]
1413

1514

1615
class ExceptionMapper:
1716
"""Exception-mapping decorator with bounded LRU caching and dynamic MRO lookup.
1817
19-
The main decorator purpose is map exception between application layers and enrich exceptions by context.
18+
The main decorator purpose is map exception between application layers and enrich
19+
exceptions by context.
2020
"""
2121

2222
def __init__(
@@ -100,7 +100,7 @@ def _get_exception_factory(
100100
self._lru_cache[exc_type] = target_exception_factory
101101
return target_exception_factory
102102

103-
# exception is not presented in base mapping, but catchall presented in mapping dict
103+
# exception is not presented in base mapping, but catchall is presented
104104
if self.exception_catchall_factory:
105105
self._lru_cache[exc_type] = self.exception_catchall_factory
106106
return self.exception_catchall_factory

app/decorators/mapper/factories.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
from abc import ABC, abstractmethod
2+
from typing import Any
23

34
from app.decorators.mapper.context import ExceptionContext
45

56

67
class ContextAwareError(Exception):
7-
def __init__(self, message: str, context: ExceptionContext = None, *args):
8+
def __init__(
9+
self, message: str, context: ExceptionContext | None = None, *args: Any
10+
):
811
super().__init__(message, *args)
912
self.context = context
1013

@@ -44,13 +47,11 @@ class EnrichedExceptionFactory(ExceptionFactory):
4447
Create and manage enriched exceptions based on a given exception type.
4548
4649
This class provides a mechanism to create exceptions dynamically,
47-
enriching them with a formatted context. It extends the behavior of
48-
the base ExceptionFactory class by incorporating the concept of a
49-
generated error type and formatted context.
50+
enriching them with a formatted context.
5051
5152
:ivar generated_error: The type of exception to generate when creating
5253
an enriched exception.
53-
:type generated_error: type[Exception]
54+
:type generated_error: type[ContextAwareError]
5455
"""
5556

5657
def __init__(self, generated_error: type[ContextAwareError]):

app/domain/entities/sample_record.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from dataclasses import dataclass
44

55

6-
76
@dataclass
87
class SampleRecord:
98
"""Record entity representing a simple record in the system."""

app/infrastructure/containers.py

Lines changed: 27 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import asyncio
2-
from importlib import import_module
32

43
from dependency_injector import containers, providers
54
from dependency_injector.providers import Callable, Factory, Singleton
65
from httpx import AsyncClient, Limits
7-
from pybotx import Bot
6+
from pybotx import Bot, HandlerCollector
87
from redis import asyncio as aioredis
98

109
from app.application.use_cases.record_use_cases import SampleRecordUseCases
@@ -20,7 +19,6 @@
2019
WriteSampleRecordUnitOfWork,
2120
)
2221
from app.logger import logger
23-
2422
from app.presentation.bot.error_handlers.internal_error_handler import (
2523
internal_error_handler,
2624
)
@@ -81,12 +79,12 @@ def __call__(self) -> asyncio.Task:
8179
return self._get_task()
8280

8381

84-
class ApplicationStartupContainer(containers.DeclarativeContainer):
85-
"""Container for application startup dependencies."""
82+
class BaseStartupContainer(containers.DeclarativeContainer):
83+
"""Общий контейнер для старта бота."""
8684

87-
redis_client = Singleton(lambda: aioredis.from_url(settings.REDIS_DSN))
85+
redis_client = providers.Singleton(lambda: aioredis.from_url(settings.REDIS_DSN))
8886

89-
redis_repo = Factory(
87+
redis_repo = providers.Factory(
9088
RedisRepo,
9189
redis=redis_client,
9290
prefix=strings.BOT_PROJECT_NAME,
@@ -107,16 +105,8 @@ class ApplicationStartupContainer(containers.DeclarativeContainer):
107105
{} if not settings.RAISE_BOT_EXCEPTIONS else {Exception: internal_error_handler}
108106
)
109107

110-
# Ленивая загрузка коллекторов
111-
@staticmethod
112-
def get_collectors():
113-
common = import_module("app.presentation.bot.commands.common")
114-
sample_record = import_module("app.presentation.bot.commands.sample_record")
115-
return [common.collector, sample_record.collector]
116-
117108
bot = providers.Singleton(
118109
Bot,
119-
collectors=Callable(get_collectors),
120110
bot_accounts=settings.BOT_CREDENTIALS,
121111
exception_handlers=exception_handlers, # type: ignore
122112
default_callback_timeout=settings.BOTX_CALLBACK_TIMEOUT_IN_SECONDS,
@@ -128,54 +118,40 @@ def get_collectors():
128118
callback_repo=callback_repo,
129119
)
130120

131-
# Используем менеджер задач для ленивой инициализации
121+
122+
class ApplicationStartupContainer(BaseStartupContainer):
123+
"""Контейнер приложения с ленивой загрузкой collectors."""
124+
125+
@staticmethod
126+
def get_collectors() -> list[HandlerCollector]:
127+
from app.presentation.bot.commands.common import collector as common_collector
128+
from app.presentation.bot.commands.sample_record import collector as sample_record_collector
129+
return [common_collector, sample_record_collector]
130+
131+
bot = providers.Singleton(
132+
Bot,
133+
collectors=providers.Callable(get_collectors),
134+
**BaseStartupContainer.bot.kwargs,
135+
)
136+
132137
callback_task_manager = providers.Singleton(
133138
CallbackTaskManager,
134-
callback_repo,
139+
BaseStartupContainer.callback_repo,
135140
)
136141

137-
# Провайдер который возвращает задачу через менеджер
138142
process_callbacks_task = providers.Callable(
139143
lambda manager: manager(),
140144
callback_task_manager,
141145
)
142146

143147

144-
class WorkerStartupContainer(containers.DeclarativeContainer):
145-
redis_client = Singleton(lambda: aioredis.from_url(settings.REDIS_DSN))
146-
147-
redis_repo = Factory(
148-
RedisRepo,
149-
redis=redis_client,
150-
prefix=strings.BOT_PROJECT_NAME,
151-
)
152-
153-
async_client = providers.Singleton(
154-
AsyncClient,
155-
timeout=settings.BOT_ASYNC_CLIENT_TIMEOUT_IN_SECONDS,
156-
limits=Limits(max_keepalive_connections=None, max_connections=None),
157-
)
148+
class WorkerStartupContainer(BaseStartupContainer):
149+
"""Контейнер воркера с прямым импортом collectors."""
158150

159-
callback_repo = providers.Singleton(
160-
CallbackRedisRepo,
161-
redis=redis_client,
162-
)
163151
from app.presentation.bot.commands import common, sample_record
164152

165-
exception_handlers = (
166-
{} if not settings.RAISE_BOT_EXCEPTIONS else {Exception: internal_error_handler}
167-
)
168-
169153
bot = providers.Singleton(
170154
Bot,
171-
collectors=[common.collector, sample_record.collector],
172-
bot_accounts=settings.BOT_CREDENTIALS,
173-
exception_handlers=exception_handlers, # type: ignore
174-
default_callback_timeout=settings.BOTX_CALLBACK_TIMEOUT_IN_SECONDS,
175-
httpx_client=async_client,
176-
middlewares=[
177-
smart_logger_middleware,
178-
answer_error_middleware,
179-
],
180-
callback_repo=callback_repo,
181-
)
155+
collectors=[common.collector, sample_record.collector], # type:ignore
156+
**BaseStartupContainer.bot.kwargs,
157+
)

app/infrastructure/db/sample_record/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Database models declarations."""
22

3-
from sqlalchemy import String, CheckConstraint
3+
from sqlalchemy import CheckConstraint, String
44
from sqlalchemy.orm import Mapped, mapped_column
55

66
from app.infrastructure.db.sqlalchemy import Base

app/infrastructure/db/sqlalchemy.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
"""SQLAlchemy helpers."""
22

3-
from asyncio import current_task
4-
from contextlib import asynccontextmanager
5-
from functools import lru_cache, wraps
6-
from typing import Any, Callable
3+
from functools import lru_cache
4+
from typing import Callable
75

86
from sqlalchemy import MetaData
97
from sqlalchemy.ext.asyncio import (
108
AsyncEngine,
119
AsyncSession,
12-
async_scoped_session,
1310
async_sessionmaker,
1411
create_async_engine,
1512
)
@@ -41,6 +38,7 @@ def make_url_sync(url: str) -> str:
4138

4239
Base = declarative_base(metadata=MetaData(naming_convention=convention))
4340

41+
4442
@lru_cache(maxsize=1)
4543
def get_engine() -> AsyncEngine:
4644
"""Lazily initialize and cache a single SQLAlchemy async engine."""
@@ -57,6 +55,7 @@ def get_session_factory() -> async_sessionmaker:
5755
engine = get_engine()
5856
return async_sessionmaker(bind=engine, expire_on_commit=False)
5957

58+
6059
#
6160
# def provide_session(func: Callable) -> Callable:
6261
# """

0 commit comments

Comments
 (0)