diff --git a/.claude/rules/code-style.md b/.claude/rules/code-style.md index 2408c84..f0e867b 100644 --- a/.claude/rules/code-style.md +++ b/.claude/rules/code-style.md @@ -625,15 +625,20 @@ except Exception: # Generic catch-all last Controllers check the result.error_code field and map to HTTP status codes: ```python +from typing import Annotated from fastapi import APIRouter, HTTPException, Depends +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container +from app.shared.infrastructure.middleware import get_current_user_id @router.post("/accounts", status_code=201) async def create_account( request: CreateAccountRequest, - handler: CreateAccountHandlerContract = Depends(get_create_account_handler), - user_id: int = Depends(get_current_user_id), + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], + user_id: Annotated[int, Depends(get_current_user_id)], ): """Create a new user account""" + handler = app_container.get_create_account_handler() + command = CreateAccountCommand( user_id=user_id, name=request.name, @@ -674,33 +679,105 @@ async def create_account( ## Dependency Injection -### Define Contract-Based Factories +**IMPORTANT**: This project uses the **ApplicationContainer** pattern for centralized dependency management. + +### Define Factory Functions in Infrastructure Layer + +Factory functions take `db: AsyncSession` and `logger: LoggerContract` as parameters and return fully configured handlers: ```python -# infrastructure/dependencies.py -def get_user_repository( - db: AsyncSession = Depends(get_db), -) -> UserRepositoryContract: - return UserRepository(db) +# infrastructure/dependencies/dependencies.py +from sqlalchemy.ext.asyncio import AsyncSession +from app.shared.domain.contracts import LoggerContract + +def login_handler_factory(db: AsyncSession, logger: LoggerContract) -> LoginHandlerContract: + """Factory for LoginHandler with all dependencies""" + from app.context.auth.infrastructure.repositories import SessionRepository + from app.context.auth.domain.services import LoginService + from app.context.auth.application.handlers import LoginHandler + + # Private helper to get repository + session_repo = _get_session_repository(db) + # Private helper to get service + login_service = _get_login_service(session_repo, logger) + + return LoginHandler(login_service, logger) -def get_login_handler( - service: LoginServiceContract = Depends(get_login_service), -) -> LoginHandlerContract: - return LoginHandler(service) +def _get_session_repository(db: AsyncSession) -> SessionRepositoryContract: + """Private helper to get repository instance""" + from app.context.auth.infrastructure.repositories import SessionRepository + return SessionRepository(db) + +def _get_login_service( + session_repo: SessionRepositoryContract, + logger: LoggerContract, +) -> LoginServiceContract: + """Private helper to get service instance""" + from app.context.auth.domain.services import LoginService + return LoginService(session_repo, logger) ``` -### Inject in Controllers +**Export factories in `__init__.py`:** + +```python +# infrastructure/dependencies/__init__.py +from .dependencies import login_handler_factory + +__all__ = ["login_handler_factory"] +``` + +### Register Factories in ApplicationContainer + +Add handler getter methods to the ApplicationContainer: ```python +# app/shared/infrastructure/container/app_container.py +class ApplicationContainer: + def __init__(self, db: AsyncSession): + self._db = db + self._logger = get_logger() + + @property + def logger(self) -> LoggerContract: + return self._logger + + def get_login_handler(self): + """Get login handler with all dependencies""" + from app.context.auth.infrastructure.dependencies import login_handler_factory + return login_handler_factory(self._db, self._logger) +``` + +### Use ApplicationContainer in Controllers + +Controllers inject the ApplicationContainer and access dependencies through it: + +```python +from typing import Annotated +from fastapi import APIRouter, Depends +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container + @router.post("/login") async def login( request: LoginRequest, - handler: LoginHandlerContract = Depends(get_login_handler), + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], ): + # Get dependencies from container + logger = app_container.logger + handler = app_container.get_login_handler() + + # Use handler command = LoginCommand(...) - return await handler.handle(command) + result = await handler.handle(command) + return result ``` +**Benefits of this pattern:** +- **Centralized dependency management** - All wiring happens in one place (ApplicationContainer) +- **Cleaner controllers** - Single container injection instead of multiple `Depends()` +- **Easier testing** - Mock the entire container instead of individual dependencies +- **Consistent across contexts** - Same pattern for all bounded contexts +- **Better separation of concerns** - Dependency wiring separated from business logic + ### Authenticating Requests **Rule:** Always inject the authenticated user ID using the shared middleware dependency. Pass user_id as a **primitive** (int) to commands/queries. @@ -708,16 +785,22 @@ async def login( **Pattern:** ```python +from typing import Annotated from fastapi import APIRouter, Depends +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id @router.post("/accounts", status_code=201) async def create_account( request: CreateAccountRequest, - handler: CreateAccountHandlerContract = Depends(get_create_account_handler), - user_id: int = Depends(get_current_user_id), # ✅ Inject authenticated user + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], + user_id: Annotated[int, Depends(get_current_user_id)], # ✅ Inject authenticated user ): """Create a new user account""" + # Get dependencies from container + logger = app_container.logger + handler = app_container.get_create_account_handler() + command = CreateAccountCommand( user_id=user_id, # ✅ Pass primitive to command name=request.name, diff --git a/.claude/rules/logging.md b/.claude/rules/logging.md index e5a6903..564cb8e 100644 --- a/.claude/rules/logging.md +++ b/.claude/rules/logging.md @@ -132,12 +132,18 @@ self._logger.critical("Unable to connect to authentication service", error=str(e ```python # ✅ GOOD - Success logged only at controller +from typing import Annotated +from fastapi import Depends +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container + @router.post("/cards") async def create_credit_card( request: CreateCreditCardRequest, - handler: CreateCreditCardHandlerContract = Depends(...), - logger: LoggerContract = Depends(get_logger), + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], ): + logger = app_container.logger + handler = app_container.get_create_credit_card_handler() + logger.info("Create credit card request", user_id=user_id, name=request.name) result = await handler.handle(command) @@ -211,15 +217,18 @@ class CreateCreditCardService: - Internal implementation details (use handler/service for that) ```python -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from typing import Annotated +from fastapi import Depends +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container @router.post("/login") async def login( request: LoginRequest, - handler: Annotated[LoginHandlerContract, Depends(get_login_handler)], - logger: Annotated[LoggerContract, Depends(get_logger)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], ): + logger = app_container.logger + handler = app_container.get_login_handler() + logger.info("Login attempt", email=str(request.email)) result = await handler.handle(LoginCommand(...)) @@ -395,30 +404,58 @@ class MyService: self._logger = logger ``` -### Step 2: Update Dependency Factory +### Step 2: Update Factory Function + +Factory functions receive logger as a parameter: ```python +from sqlalchemy.ext.asyncio import AsyncSession from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger -def get_my_service( - some_repo: Annotated[SomeRepositoryContract, Depends(get_some_repo)], - logger: Annotated[LoggerContract, Depends(get_logger)], # Add logger dependency +def my_handler_factory(db: AsyncSession, logger: LoggerContract) -> MyHandlerContract: + """Factory for MyHandler with all dependencies""" + some_repo = _get_some_repository(db) + my_service = _get_my_service(some_repo, logger) # Pass logger to service + return MyHandler(my_service, logger) # Pass logger to handler + +def _get_my_service( + some_repo: SomeRepositoryContract, + logger: LoggerContract, ) -> MyServiceContract: + """Private helper to get service instance""" return MyService(some_repo, logger) ``` -### Step 3: Controllers Get Logger Directly +### Step 3: Register in ApplicationContainer -Controllers inject logger as a parameter (not passed through handlers): +Add handler getter method to ApplicationContainer: ```python +# app/shared/infrastructure/container/app_container.py +class ApplicationContainer: + def get_my_handler(self): + """Get my handler with all dependencies""" + from app.context.my_context.infrastructure.dependencies import my_handler_factory + return my_handler_factory(self._db, self._logger) +``` + +### Step 4: Controllers Get Logger from Container + +Controllers access logger through the ApplicationContainer: + +```python +from typing import Annotated +from fastapi import Depends +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container + @router.post("/endpoint") async def my_endpoint( request: MyRequest, - handler: Annotated[MyHandlerContract, Depends(get_my_handler)], - logger: Annotated[LoggerContract, Depends(get_logger)], # Inject logger + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], ): + logger = app_container.logger + handler = app_container.get_my_handler() + logger.info("Request received", some_field=request.some_field) result = await handler.handle(...) logger.info("Request completed", result_status=result.status) diff --git a/CLAUDE.md b/CLAUDE.md index 4f1246f..4b3d70b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,7 +80,9 @@ app/context/{context_name}/ │ ├── repositories/ # Data access implementations │ ├── models/ # SQLAlchemy ORM models │ ├── mappers/ # Domain DTO ↔ Database model mappers -│ └── dependencies.py # Dependency injection setup +│ └── dependencies/ # Dependency injection factory functions +│ ├── dependencies.py +│ └── __init__.py │ └── interface/ # External interfaces └── rest/ # REST API layer @@ -116,19 +118,65 @@ Located at `app/shared/`, contains cross-cutting concerns: - Cross-context communication patterns - Common anti-patterns to avoid -### 1. Dependency Injection via FastAPI +### 1. Dependency Injection via ApplicationContainer -Dependencies are defined as factory functions in `infrastructure/dependencies.py`: +Dependencies are managed through a centralized **ApplicationContainer** (`app/shared/infrastructure/container/app_container.py`) that provides all handlers with their dependencies already wired. + +**Infrastructure Layer** - Define factory functions in `infrastructure/dependencies/dependencies.py`: + +```python +# Factory pattern: Takes db and logger, returns fully configured handler +def login_handler_factory(db: AsyncSession, logger: LoggerContract) -> LoginHandlerContract: + """Factory for LoginHandler with all dependencies""" + from app.context.auth.infrastructure.repositories import SessionRepository + from app.context.auth.domain.services import LoginService + from app.context.auth.application.handlers import LoginHandler + from app.context.user.infrastructure.dependencies import find_user_handler_factory + + # Wire up dependencies + session_repo = SessionRepository(db) + login_service = LoginService(session_repo, logger) + user_handler = find_user_handler_factory(db, logger) + + return LoginHandler(user_handler, login_service, logger) +``` + +**ApplicationContainer** - Centralizes dependency management: + +```python +# app/shared/infrastructure/container/app_container.py +class ApplicationContainer: + def __init__(self, db: AsyncSession): + self._db = db + self._logger = get_logger() + + @property + def logger(self) -> LoggerContract: + return self._logger + + def get_login_handler(self): + """Get login handler with all dependencies""" + from app.context.auth.infrastructure.dependencies import login_handler_factory + return login_handler_factory(self._db, self._logger) +``` + +**Controllers** - Inject ApplicationContainer via FastAPI: ```python -# Pattern: Contract-based injection -def get_service() -> ServiceContract: - return ConcreteService() - -def get_handler( - service: ServiceContract = Depends(get_service), -) -> HandlerContract: - return ConcreteHandler(service) +from typing import Annotated +from fastapi import APIRouter, Depends +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container + +@router.post("/login") +async def login( + request: LoginRequest, + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], +): + logger = app_container.logger + handler = app_container.get_login_handler() + + command = LoginCommand(...) + return await handler.handle(command) ``` **Important**: Always program to contracts (interfaces), not implementations. @@ -269,15 +317,21 @@ class UserAccountDTO: ```python # Controller - app/context/user_account/interface/rest/controllers/create_account_controller.py +from typing import Annotated from fastapi import APIRouter, Depends +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id @router.post("/accounts", status_code=201) async def create_account( request: CreateAccountRequest, - handler: CreateAccountHandlerContract = Depends(get_create_account_handler), - user_id: int = Depends(get_current_user_id), # ✅ Inject authenticated user ID + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], + user_id: Annotated[int, Depends(get_current_user_id)], # ✅ Inject authenticated user ID ): + # Get dependencies from container + logger = app_container.logger + handler = app_container.get_create_account_handler() + # Pass primitive user_id to command command = CreateAccountCommand( user_id=user_id, # ✅ Primitive in command @@ -638,12 +692,16 @@ When implementing a new feature: - Database models (if needed) - Repositories - Mappers - - Dependencies -5. **Add interface layer**: + - Factory functions in `dependencies/dependencies.py` + - Export factories in `dependencies/__init__.py` +5. **Register in ApplicationContainer**: + - Add handler getter method(s) to `app/shared/infrastructure/container/app_container.py` + - Method should call the factory function with `self._db` and `self._logger` +6. **Add interface layer**: - Pydantic schemas - - Controllers + - Controllers (using `ApplicationContainer` pattern) - Route registration in `app/main.py` -6. **Create database migration**: +7. **Create database migration**: - `just migration-generate "description"` - `just migrate` diff --git a/app/context/auth/infrastructure/dependencies.py b/app/context/auth/infrastructure/dependencies.py deleted file mode 100644 index 2dbfbb4..0000000 --- a/app/context/auth/infrastructure/dependencies.py +++ /dev/null @@ -1,61 +0,0 @@ -from typing import Annotated - -from fastapi import Depends -from sqlalchemy.ext.asyncio import AsyncSession - -from app.context.auth.application.contracts import ( - LoginHandlerContract, -) -from app.context.auth.application.contracts.get_session_handler_contract import ( - GetSessionHandlerContract, -) -from app.context.auth.application.handlers import LoginHandler -from app.context.auth.application.handlers.get_session_handler import GetSessionHandler -from app.context.auth.domain.contracts import ( - LoginServiceContract, - SessionRepositoryContract, -) -from app.context.auth.domain.services import LoginService -from app.context.auth.infrastructure.repositories import SessionRepository -from app.context.user.application.contracts import ( - FindUserHandlerContract, -) -from app.context.user.infrastructure.dependencies import ( - get_find_user_query_handler, -) -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.database import get_db -from app.shared.infrastructure.dependencies import get_logger - - -def get_session_repository( - db: Annotated[AsyncSession, Depends(get_db)], -) -> SessionRepositoryContract: - return SessionRepository(db) - - -def get_login_service( - session_repo: Annotated[SessionRepositoryContract, Depends(get_session_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> LoginServiceContract: - """ - LoginService dependency injection - """ - return LoginService(session_repo, logger) - - -def get_session_handler( - session_repo: Annotated[SessionRepository, Depends(get_session_repository)], -) -> GetSessionHandlerContract: - return GetSessionHandler(session_repo) - - -def get_login_handler( - user_query_handler: Annotated[FindUserHandlerContract, Depends(get_find_user_query_handler)], - login_service: Annotated[LoginServiceContract, Depends(get_login_service)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> LoginHandlerContract: - """ - LoginHandler dependency injection - """ - return LoginHandler(user_query_handler, login_service, logger) diff --git a/app/context/auth/infrastructure/dependencies/__init__.py b/app/context/auth/infrastructure/dependencies/__init__.py new file mode 100644 index 0000000..6b5c76c --- /dev/null +++ b/app/context/auth/infrastructure/dependencies/__init__.py @@ -0,0 +1,3 @@ +from .dependencies import login_handler_factory + +__all__ = ["login_handler_factory"] diff --git a/app/context/auth/infrastructure/dependencies/dependencies.py b/app/context/auth/infrastructure/dependencies/dependencies.py new file mode 100644 index 0000000..6015105 --- /dev/null +++ b/app/context/auth/infrastructure/dependencies/dependencies.py @@ -0,0 +1,35 @@ +from sqlalchemy.ext.asyncio import AsyncSession + +from app.context.auth.application.contracts import ( + LoginHandlerContract, +) +from app.context.auth.application.handlers import LoginHandler +from app.context.auth.domain.contracts import ( + LoginServiceContract, + SessionRepositoryContract, +) +from app.context.auth.domain.services import LoginService +from app.shared.domain.contracts import LoggerContract + + +def login_handler_factory(db: AsyncSession, logger: LoggerContract) -> LoginHandlerContract: + from app.context.user.infrastructure.dependencies import ( + find_user_handler_factory, + ) + + login_service = _get_login_service(_get_session_repository(db), logger) + user_query_handler = find_user_handler_factory(db, logger) + return LoginHandler(user_query_handler, login_service, logger) + + +def _get_session_repository(db: AsyncSession) -> SessionRepositoryContract: + from app.context.auth.infrastructure.repositories import SessionRepository + + return SessionRepository(db) + + +def _get_login_service(session_repository: SessionRepositoryContract, logger: LoggerContract) -> LoginServiceContract: + return LoginService(session_repository, logger) + + +# ------------------------------------------------------------------------- diff --git a/app/context/auth/interface/rest/controllers/login_rest_controller.py b/app/context/auth/interface/rest/controllers/login_rest_controller.py index 0eae356..30be900 100644 --- a/app/context/auth/interface/rest/controllers/login_rest_controller.py +++ b/app/context/auth/interface/rest/controllers/login_rest_controller.py @@ -3,13 +3,10 @@ from fastapi import APIRouter, Depends, HTTPException, Response from app.context.auth.application.commands import LoginCommand -from app.context.auth.application.contracts import LoginHandlerContract from app.context.auth.application.dto import LoginHandlerResultStatus -from app.context.auth.infrastructure.dependencies import get_login_handler from app.context.auth.interface.rest.schemas import LoginRequest, LoginResponse -from app.shared.domain.contracts import LoggerContract from app.shared.domain.value_objects import SharedAppEnv -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container router = APIRouter(prefix="/login") @@ -18,9 +15,11 @@ async def login( response: Response, request: LoginRequest, - handler: Annotated[LoginHandlerContract, Depends(get_login_handler)], - logger: Annotated[LoggerContract, Depends(get_logger)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], ): + logger = app_container.logger + handler = app_container.get_login_handler() + """User login endpoint""" logger.info("Login attempt", email=str(request.email)) diff --git a/app/context/credit_card/infrastructure/dependencies/__init__.py b/app/context/credit_card/infrastructure/dependencies/__init__.py new file mode 100644 index 0000000..c0f3628 --- /dev/null +++ b/app/context/credit_card/infrastructure/dependencies/__init__.py @@ -0,0 +1,15 @@ +from .dependencies import ( + create_credit_card_handler_factory, + delete_credit_card_handler_factory, + find_credit_card_by_id_handler_factory, + find_credit_cards_by_user_handler_factory, + update_credit_card_handler_factory, +) + +__all__ = [ + "create_credit_card_handler_factory", + "delete_credit_card_handler_factory", + "find_credit_card_by_id_handler_factory", + "find_credit_cards_by_user_handler_factory", + "update_credit_card_handler_factory", +] diff --git a/app/context/credit_card/infrastructure/dependencies.py b/app/context/credit_card/infrastructure/dependencies/dependencies.py similarity index 65% rename from app/context/credit_card/infrastructure/dependencies.py rename to app/context/credit_card/infrastructure/dependencies/dependencies.py index 091400c..c42e9f0 100644 --- a/app/context/credit_card/infrastructure/dependencies.py +++ b/app/context/credit_card/infrastructure/dependencies/dependencies.py @@ -1,6 +1,3 @@ -from typing import Annotated - -from fastapi import Depends from sqlalchemy.ext.asyncio import AsyncSession from app.context.credit_card.application.contracts.create_credit_card_handler_contract import ( @@ -28,87 +25,48 @@ UpdateCreditCardServiceContract, ) from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.database import get_db -from app.shared.infrastructure.dependencies import get_logger - -# ───────────────────────────────────────────────────────────────── -# REPOSITORY -# ───────────────────────────────────────────────────────────────── - - -def get_credit_card_repository( - db: Annotated[AsyncSession, Depends(get_db)], -) -> CreditCardRepositoryContract: - """CreditCardRepository dependency injection""" - from app.context.credit_card.infrastructure.repositories.credit_card_repository import ( - CreditCardRepository, - ) - - return CreditCardRepository(db) - # ───────────────────────────────────────────────────────────────── # COMMAND HANDLERS (Write operations) # ───────────────────────────────────────────────────────────────── -def get_create_credit_card_service( - card_repository: Annotated[CreditCardRepositoryContract, Depends(get_credit_card_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> CreateCreditCardServiceContract: - """CreateCreditCardService dependency injection""" - from app.context.credit_card.domain.services.create_credit_card_service import ( - CreateCreditCardService, - ) - - return CreateCreditCardService(card_repository, logger) - - -def get_create_credit_card_handler( - service: Annotated[CreateCreditCardServiceContract, Depends(get_create_credit_card_service)], - logger: Annotated[LoggerContract, Depends(get_logger)], +def create_credit_card_handler_factory( + db: AsyncSession, + logger: LoggerContract, ) -> CreateCreditCardHandlerContract: - """CreateCreditCardHandler dependency injection""" + """Factory for CreateCreditCardHandler with all dependencies""" from app.context.credit_card.application.handlers.create_credit_card_handler import ( CreateCreditCardHandler, ) + service = _get_create_credit_card_service(_get_credit_card_repository(db), logger) return CreateCreditCardHandler(service, logger) -def get_update_credit_card_service( - repository: Annotated[CreditCardRepositoryContract, Depends(get_credit_card_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> UpdateCreditCardServiceContract: - """UpdateCreditCardService dependency injection""" - from app.context.credit_card.domain.services.update_credit_card_service import ( - UpdateCreditCardService, - ) - - return UpdateCreditCardService(repository, logger) - - -def get_update_credit_card_handler( - service: Annotated[UpdateCreditCardServiceContract, Depends(get_update_credit_card_service)], - logger: Annotated[LoggerContract, Depends(get_logger)], +def update_credit_card_handler_factory( + db: AsyncSession, + logger: LoggerContract, ) -> UpdateCreditCardHandlerContract: - """UpdateCreditCardHandler dependency injection""" + """Factory for UpdateCreditCardHandler with all dependencies""" from app.context.credit_card.application.handlers.update_credit_card_handler import ( UpdateCreditCardHandler, ) + service = _get_update_credit_card_service(_get_credit_card_repository(db), logger) return UpdateCreditCardHandler(service, logger) -def get_delete_credit_card_handler( - repository: Annotated[CreditCardRepositoryContract, Depends(get_credit_card_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], +def delete_credit_card_handler_factory( + db: AsyncSession, + logger: LoggerContract, ) -> DeleteCreditCardHandlerContract: - """DeleteCreditCardHandler dependency injection""" + """Factory for DeleteCreditCardHandler with all dependencies""" from app.context.credit_card.application.handlers.delete_credit_card_handler import ( DeleteCreditCardHandler, ) + repository = _get_credit_card_repository(db) return DeleteCreditCardHandler(repository, logger) @@ -117,25 +75,65 @@ def get_delete_credit_card_handler( # ───────────────────────────────────────────────────────────────── -def get_find_credit_card_by_id_handler( - repository: Annotated[CreditCardRepositoryContract, Depends(get_credit_card_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], +def find_credit_card_by_id_handler_factory( + db: AsyncSession, + logger: LoggerContract, ) -> FindCreditCardByIdHandlerContract: - """FindCreditCardByIdHandler dependency injection""" + """Factory for FindCreditCardByIdHandler with all dependencies""" from app.context.credit_card.application.handlers.find_credit_card_by_id_handler import ( FindCreditCardByIdHandler, ) + repository = _get_credit_card_repository(db) return FindCreditCardByIdHandler(repository, logger) -def get_find_credit_cards_by_user_handler( - repository: Annotated[CreditCardRepositoryContract, Depends(get_credit_card_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], +def find_credit_cards_by_user_handler_factory( + db: AsyncSession, + logger: LoggerContract, ) -> FindCreditCardsByUserHandlerContract: - """FindCreditCardsByUserHandler dependency injection""" + """Factory for FindCreditCardsByUserHandler with all dependencies""" from app.context.credit_card.application.handlers.find_credit_cards_by_user_handler import ( FindCreditCardsByUserHandler, ) + repository = _get_credit_card_repository(db) return FindCreditCardsByUserHandler(repository, logger) + + +# ───────────────────────────────────────────────────────────────── +# Private helper functions +# ───────────────────────────────────────────────────────────────── + + +def _get_credit_card_repository(db: AsyncSession) -> CreditCardRepositoryContract: + """Get credit card repository instance""" + from app.context.credit_card.infrastructure.repositories.credit_card_repository import ( + CreditCardRepository, + ) + + return CreditCardRepository(db) + + +def _get_create_credit_card_service( + repository: CreditCardRepositoryContract, + logger: LoggerContract, +) -> CreateCreditCardServiceContract: + """Get create credit card service instance""" + from app.context.credit_card.domain.services.create_credit_card_service import ( + CreateCreditCardService, + ) + + return CreateCreditCardService(repository, logger) + + +def _get_update_credit_card_service( + repository: CreditCardRepositoryContract, + logger: LoggerContract, +) -> UpdateCreditCardServiceContract: + """Get update credit card service instance""" + from app.context.credit_card.domain.services.update_credit_card_service import ( + UpdateCreditCardService, + ) + + return UpdateCreditCardService(repository, logger) diff --git a/app/context/credit_card/interface/rest/controllers/create_credit_card_controller.py b/app/context/credit_card/interface/rest/controllers/create_credit_card_controller.py index f272237..e1e4807 100644 --- a/app/context/credit_card/interface/rest/controllers/create_credit_card_controller.py +++ b/app/context/credit_card/interface/rest/controllers/create_credit_card_controller.py @@ -3,22 +3,15 @@ from fastapi import APIRouter, Depends, HTTPException from app.context.credit_card.application.commands import CreateCreditCardCommand -from app.context.credit_card.application.contracts import ( - CreateCreditCardHandlerContract, -) from app.context.credit_card.application.dto import CreateCreditCardErrorCode from app.context.credit_card.domain.value_objects import CardLimit -from app.context.credit_card.infrastructure.dependencies import ( - get_create_credit_card_handler, -) from app.context.credit_card.interface.schemas.create_credit_card_response import ( CreateCreditCardResponse, ) from app.context.credit_card.interface.schemas.create_credit_card_schema import ( CreateCreditCardRequest, ) -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/cards") @@ -27,11 +20,13 @@ @router.post("", response_model=CreateCreditCardResponse, status_code=201) async def create_credit_card( request: CreateCreditCardRequest, - handler: Annotated[CreateCreditCardHandlerContract, Depends(get_create_credit_card_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Create a new credit card""" + logger = app_container.logger + handler = app_container.get_create_credit_card_handler() + logger.info("Create credit card request", user_id=user_id, account_id=request.account_id, name=request.name) command = CreateCreditCardCommand( diff --git a/app/context/credit_card/interface/rest/controllers/delete_credit_card_controller.py b/app/context/credit_card/interface/rest/controllers/delete_credit_card_controller.py index 5120c21..a4b9c3e 100644 --- a/app/context/credit_card/interface/rest/controllers/delete_credit_card_controller.py +++ b/app/context/credit_card/interface/rest/controllers/delete_credit_card_controller.py @@ -3,15 +3,8 @@ from fastapi import APIRouter, Depends, HTTPException, status from app.context.credit_card.application.commands import DeleteCreditCardCommand -from app.context.credit_card.application.contracts import ( - DeleteCreditCardHandlerContract, -) from app.context.credit_card.application.dto import DeleteCreditCardErrorCode -from app.context.credit_card.infrastructure.dependencies import ( - get_delete_credit_card_handler, -) -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/cards") @@ -20,11 +13,13 @@ @router.delete("/{credit_card_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_credit_card( credit_card_id: int, - handler: Annotated[DeleteCreditCardHandlerContract, Depends(get_delete_credit_card_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Delete a credit card (soft delete)""" + logger = app_container.logger + handler = app_container.get_delete_credit_card_handler() + logger.info("Delete credit card request", user_id=user_id, credit_card_id=credit_card_id) command = DeleteCreditCardCommand( diff --git a/app/context/credit_card/interface/rest/controllers/find_credit_card_by_id_controller.py b/app/context/credit_card/interface/rest/controllers/find_credit_card_by_id_controller.py index 82facf2..80da313 100644 --- a/app/context/credit_card/interface/rest/controllers/find_credit_card_by_id_controller.py +++ b/app/context/credit_card/interface/rest/controllers/find_credit_card_by_id_controller.py @@ -2,18 +2,11 @@ from fastapi import APIRouter, Depends, HTTPException -from app.context.credit_card.application.contracts import ( - FindCreditCardByIdHandlerContract, -) from app.context.credit_card.application.queries import FindCreditCardByIdQuery -from app.context.credit_card.infrastructure.dependencies import ( - get_find_credit_card_by_id_handler, -) from app.context.credit_card.interface.schemas.credit_card_response import ( CreditCardResponse, ) -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/cards") @@ -22,11 +15,13 @@ @router.get("/{credit_card_id}", response_model=CreditCardResponse) async def get_credit_card( credit_card_id: int, - handler: Annotated[FindCreditCardByIdHandlerContract, Depends(get_find_credit_card_by_id_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Get a credit card by ID""" + logger = app_container.logger + handler = app_container.get_find_credit_card_by_id_handler() + logger.info("Get credit card by ID request", user_id=user_id, credit_card_id=credit_card_id) query = FindCreditCardByIdQuery( diff --git a/app/context/credit_card/interface/rest/controllers/find_credit_cards_by_user_controller.py b/app/context/credit_card/interface/rest/controllers/find_credit_cards_by_user_controller.py index 9567ae8..d7496cd 100644 --- a/app/context/credit_card/interface/rest/controllers/find_credit_cards_by_user_controller.py +++ b/app/context/credit_card/interface/rest/controllers/find_credit_cards_by_user_controller.py @@ -2,18 +2,11 @@ from fastapi import APIRouter, Depends -from app.context.credit_card.application.contracts import ( - FindCreditCardsByUserHandlerContract, -) from app.context.credit_card.application.queries import FindCreditCardsByUserQuery -from app.context.credit_card.infrastructure.dependencies import ( - get_find_credit_cards_by_user_handler, -) from app.context.credit_card.interface.schemas.credit_card_response import ( CreditCardResponse, ) -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/cards") @@ -21,11 +14,13 @@ @router.get("", response_model=list[CreditCardResponse]) async def get_credit_cards( - handler: Annotated[FindCreditCardsByUserHandlerContract, Depends(get_find_credit_cards_by_user_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Get all credit cards for the current user""" + logger = app_container.logger + handler = app_container.get_find_credit_cards_by_user_handler() + logger.info("Get all credit cards for user request", user_id=user_id) query = FindCreditCardsByUserQuery(user_id=user_id) diff --git a/app/context/credit_card/interface/rest/controllers/update_credit_card_controller.py b/app/context/credit_card/interface/rest/controllers/update_credit_card_controller.py index c26af55..26c01b6 100644 --- a/app/context/credit_card/interface/rest/controllers/update_credit_card_controller.py +++ b/app/context/credit_card/interface/rest/controllers/update_credit_card_controller.py @@ -3,21 +3,14 @@ from fastapi import APIRouter, Depends, HTTPException from app.context.credit_card.application.commands import UpdateCreditCardCommand -from app.context.credit_card.application.contracts import ( - UpdateCreditCardHandlerContract, -) from app.context.credit_card.application.dto import UpdateCreditCardErrorCode -from app.context.credit_card.infrastructure.dependencies import ( - get_update_credit_card_handler, -) from app.context.credit_card.interface.schemas.update_credit_card_response import ( UpdateCreditCardResponse, ) from app.context.credit_card.interface.schemas.update_credit_card_schema import ( UpdateCreditCardRequest, ) -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/cards") @@ -27,11 +20,13 @@ async def update_credit_card( credit_card_id: int, request: UpdateCreditCardRequest, - handler: Annotated[UpdateCreditCardHandlerContract, Depends(get_update_credit_card_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Update an existing credit card""" + logger = app_container.logger + handler = app_container.get_update_credit_card_handler() + logger.info("Update credit card request", user_id=user_id, credit_card_id=credit_card_id) command = UpdateCreditCardCommand( diff --git a/app/context/entry/infrastructure/dependencies.py b/app/context/entry/infrastructure/dependencies.py deleted file mode 100644 index 119dfde..0000000 --- a/app/context/entry/infrastructure/dependencies.py +++ /dev/null @@ -1,102 +0,0 @@ -from typing import Annotated - -from fastapi import Depends -from sqlalchemy.ext.asyncio import AsyncSession - -from app.context.entry.application.contracts import ( - CreateEntryHandlerContract, - DeleteEntryHandlerContract, - FindEntriesByAccountMonthHandlerContract, - FindEntryByIdHandlerContract, - UpdateEntryHandlerContract, -) -from app.context.entry.domain.contracts import ( - CreateEntryServiceContract, - EntryRepositoryContract, - UpdateEntryServiceContract, -) -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.database import get_db -from app.shared.infrastructure.dependencies import get_logger - - -# Repository -def get_entry_repository( - db: Annotated[AsyncSession, Depends(get_db)], -) -> EntryRepositoryContract: - """Get entry repository""" - from app.context.entry.infrastructure.repositories import EntryRepository - - return EntryRepository(db) - - -# Services -def get_create_entry_service( - repository: Annotated[EntryRepositoryContract, Depends(get_entry_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> CreateEntryServiceContract: - """Get create entry service""" - from app.context.entry.domain.services import CreateEntryService - - return CreateEntryService(repository, logger) - - -def get_update_entry_service( - repository: Annotated[EntryRepositoryContract, Depends(get_entry_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> UpdateEntryServiceContract: - """Get update entry service""" - from app.context.entry.domain.services import UpdateEntryService - - return UpdateEntryService(repository, logger) - - -# Handlers -def get_create_entry_handler( - service: Annotated[CreateEntryServiceContract, Depends(get_create_entry_service)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> CreateEntryHandlerContract: - """Get create entry handler""" - from app.context.entry.application.handlers import CreateEntryHandler - - return CreateEntryHandler(service, logger) - - -def get_find_entry_by_id_handler( - repository: Annotated[EntryRepositoryContract, Depends(get_entry_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> FindEntryByIdHandlerContract: - """Get find entry by ID handler""" - from app.context.entry.application.handlers import FindEntryByIdHandler - - return FindEntryByIdHandler(repository, logger) - - -def get_find_entries_by_account_month_handler( - repository: Annotated[EntryRepositoryContract, Depends(get_entry_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> FindEntriesByAccountMonthHandlerContract: - """Get find entries by account and month handler""" - from app.context.entry.application.handlers import FindEntriesByAccountMonthHandler - - return FindEntriesByAccountMonthHandler(repository, logger) - - -def get_update_entry_handler( - service: Annotated[UpdateEntryServiceContract, Depends(get_update_entry_service)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> UpdateEntryHandlerContract: - """Get update entry handler""" - from app.context.entry.application.handlers import UpdateEntryHandler - - return UpdateEntryHandler(service, logger) - - -def get_delete_entry_handler( - repository: Annotated[EntryRepositoryContract, Depends(get_entry_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> DeleteEntryHandlerContract: - """Get delete entry handler""" - from app.context.entry.application.handlers import DeleteEntryHandler - - return DeleteEntryHandler(repository, logger) diff --git a/app/context/entry/infrastructure/dependencies/__init__.py b/app/context/entry/infrastructure/dependencies/__init__.py new file mode 100644 index 0000000..d8c2b3c --- /dev/null +++ b/app/context/entry/infrastructure/dependencies/__init__.py @@ -0,0 +1,16 @@ +from .dependencies import ( + create_entry_handler_factory, + delete_entry_handler_factory, + find_entries_by_account_month_handler_factory, + find_entry_by_id_handler_factory, + update_entry_handler_factory, +) + +__all__ = [ + "create_entry_handler_factory", + "delete_entry_handler_factory", + "update_entry_handler_factory", + "delete_entry_handler_factory", + "find_entry_by_id_handler_factory", + "find_entries_by_account_month_handler_factory", +] diff --git a/app/context/entry/infrastructure/dependencies/dependencies.py b/app/context/entry/infrastructure/dependencies/dependencies.py new file mode 100644 index 0000000..28fa5d7 --- /dev/null +++ b/app/context/entry/infrastructure/dependencies/dependencies.py @@ -0,0 +1,111 @@ +from sqlalchemy.ext.asyncio import AsyncSession + +from app.context.entry.application.contracts import ( + CreateEntryHandlerContract, + DeleteEntryHandlerContract, + FindEntriesByAccountMonthHandlerContract, + FindEntryByIdHandlerContract, + UpdateEntryHandlerContract, +) +from app.context.entry.domain.contracts import ( + CreateEntryServiceContract, + EntryRepositoryContract, + UpdateEntryServiceContract, +) +from app.shared.domain.contracts import LoggerContract + +# ───────────────────────────────────────────────────────────────── +# COMMAND HANDLERS (Write operations) +# ───────────────────────────────────────────────────────────────── + + +def create_entry_handler_factory( + db: AsyncSession, + logger: LoggerContract, +) -> CreateEntryHandlerContract: + """Factory for CreateEntryHandler with all dependencies""" + from app.context.entry.application.handlers import CreateEntryHandler + + service = _get_create_entry_service(_get_entry_repository(db), logger) + return CreateEntryHandler(service, logger) + + +def update_entry_handler_factory( + db: AsyncSession, + logger: LoggerContract, +) -> UpdateEntryHandlerContract: + """Factory for UpdateEntryHandler with all dependencies""" + from app.context.entry.application.handlers import UpdateEntryHandler + + service = _get_update_entry_service(_get_entry_repository(db), logger) + return UpdateEntryHandler(service, logger) + + +def delete_entry_handler_factory( + db: AsyncSession, + logger: LoggerContract, +) -> DeleteEntryHandlerContract: + """Factory for DeleteEntryHandler with all dependencies""" + from app.context.entry.application.handlers import DeleteEntryHandler + + repository = _get_entry_repository(db) + return DeleteEntryHandler(repository, logger) + + +# ───────────────────────────────────────────────────────────────── +# QUERY HANDLERS (Read operations) +# ───────────────────────────────────────────────────────────────── + + +def find_entry_by_id_handler_factory( + db: AsyncSession, + logger: LoggerContract, +) -> FindEntryByIdHandlerContract: + """Factory for FindEntryByIdHandler with all dependencies""" + from app.context.entry.application.handlers import FindEntryByIdHandler + + repository = _get_entry_repository(db) + return FindEntryByIdHandler(repository, logger) + + +def find_entries_by_account_month_handler_factory( + db: AsyncSession, + logger: LoggerContract, +) -> FindEntriesByAccountMonthHandlerContract: + """Factory for FindEntriesByAccountMonthHandler with all dependencies""" + from app.context.entry.application.handlers import FindEntriesByAccountMonthHandler + + repository = _get_entry_repository(db) + return FindEntriesByAccountMonthHandler(repository, logger) + + +# ───────────────────────────────────────────────────────────────── +# Private helper functions +# ───────────────────────────────────────────────────────────────── + + +def _get_entry_repository(db: AsyncSession) -> EntryRepositoryContract: + """Get entry repository instance""" + from app.context.entry.infrastructure.repositories import EntryRepository + + return EntryRepository(db) + + +def _get_create_entry_service( + repository: EntryRepositoryContract, + logger: LoggerContract, +) -> CreateEntryServiceContract: + """Get create entry service instance""" + from app.context.entry.domain.services import CreateEntryService + + return CreateEntryService(repository, logger) + + +def _get_update_entry_service( + repository: EntryRepositoryContract, + logger: LoggerContract, +) -> UpdateEntryServiceContract: + """Get update entry service instance""" + from app.context.entry.domain.services import UpdateEntryService + + return UpdateEntryService(repository, logger) diff --git a/app/context/entry/interface/rest/controllers/create_entry_controller.py b/app/context/entry/interface/rest/controllers/create_entry_controller.py index 68584d6..9a718e9 100644 --- a/app/context/entry/interface/rest/controllers/create_entry_controller.py +++ b/app/context/entry/interface/rest/controllers/create_entry_controller.py @@ -3,12 +3,9 @@ from fastapi import APIRouter, Depends, HTTPException from app.context.entry.application.commands import CreateEntryCommand -from app.context.entry.application.contracts import CreateEntryHandlerContract from app.context.entry.application.dto import CreateEntryErrorCode -from app.context.entry.infrastructure.dependencies import get_create_entry_handler from app.context.entry.interface.schemas import CreateEntryRequest, CreateEntryResponse -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/entries") @@ -17,11 +14,13 @@ @router.post("", response_model=CreateEntryResponse, status_code=201) async def create_entry( request: CreateEntryRequest, - handler: Annotated[CreateEntryHandlerContract, Depends(get_create_entry_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Create a new entry""" + logger = app_container.logger + handler = app_container.get_create_entry_handler() + logger.info("Create entry request", user_id=user_id, account_id=request.account_id) command = CreateEntryCommand( diff --git a/app/context/entry/interface/rest/controllers/delete_entry_controller.py b/app/context/entry/interface/rest/controllers/delete_entry_controller.py index 3ced140..7563a00 100644 --- a/app/context/entry/interface/rest/controllers/delete_entry_controller.py +++ b/app/context/entry/interface/rest/controllers/delete_entry_controller.py @@ -3,11 +3,8 @@ from fastapi import APIRouter, Depends, HTTPException from app.context.entry.application.commands import DeleteEntryCommand -from app.context.entry.application.contracts import DeleteEntryHandlerContract from app.context.entry.application.dto import DeleteEntryErrorCode -from app.context.entry.infrastructure.dependencies import get_delete_entry_handler -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/entries") @@ -16,11 +13,13 @@ @router.delete("/{entry_id}", status_code=204) async def delete_entry( entry_id: int, - handler: Annotated[DeleteEntryHandlerContract, Depends(get_delete_entry_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Delete an entry (hard delete)""" + logger = app_container.logger + handler = app_container.get_delete_entry_handler() + logger.info("Delete entry request", user_id=user_id, entry_id=entry_id) command = DeleteEntryCommand(entry_id=entry_id, user_id=user_id) diff --git a/app/context/entry/interface/rest/controllers/find_entry_controller.py b/app/context/entry/interface/rest/controllers/find_entry_controller.py index b8868a5..8da7e27 100644 --- a/app/context/entry/interface/rest/controllers/find_entry_controller.py +++ b/app/context/entry/interface/rest/controllers/find_entry_controller.py @@ -2,10 +2,6 @@ from fastapi import APIRouter, Depends, HTTPException, Query -from app.context.entry.application.contracts import ( - FindEntriesByAccountMonthHandlerContract, - FindEntryByIdHandlerContract, -) from app.context.entry.application.dto import ( FindMultipleEntriesErrorCode, FindSingleEntryErrorCode, @@ -14,13 +10,8 @@ FindEntriesByAccountMonthQuery, FindEntryByIdQuery, ) -from app.context.entry.infrastructure.dependencies import ( - get_find_entries_by_account_month_handler, - get_find_entry_by_id_handler, -) from app.context.entry.interface.schemas import EntryResponse -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/entries") @@ -29,11 +20,13 @@ @router.get("/{entry_id}", response_model=EntryResponse) async def get_entry( entry_id: int, - handler: Annotated[FindEntryByIdHandlerContract, Depends(get_find_entry_by_id_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Get a specific entry by ID""" + logger = app_container.logger + handler = app_container.get_find_entry_by_id_handler() + query = FindEntryByIdQuery(entry_id=entry_id, user_id=user_id) result = await handler.handle(query) @@ -72,11 +65,13 @@ async def list_entries( account_id: Annotated[int, Query(gt=0, description="Account ID (required)")], month: Annotated[int, Query(ge=1, le=12, description="Month (1-12, required)")], year: Annotated[int, Query(ge=1900, le=2100, description="Year (e.g., 2025, required)")], - handler: Annotated[FindEntriesByAccountMonthHandlerContract, Depends(get_find_entries_by_account_month_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Get all entries for a specific account in a given month/year""" + logger = app_container.logger + handler = app_container.get_find_entries_by_account_month_handler() + query = FindEntriesByAccountMonthQuery( user_id=user_id, account_id=account_id, diff --git a/app/context/entry/interface/rest/controllers/update_entry_controller.py b/app/context/entry/interface/rest/controllers/update_entry_controller.py index 626bba3..256d73c 100644 --- a/app/context/entry/interface/rest/controllers/update_entry_controller.py +++ b/app/context/entry/interface/rest/controllers/update_entry_controller.py @@ -3,12 +3,9 @@ from fastapi import APIRouter, Depends, HTTPException from app.context.entry.application.commands import UpdateEntryCommand -from app.context.entry.application.contracts import UpdateEntryHandlerContract from app.context.entry.application.dto import UpdateEntryErrorCode -from app.context.entry.infrastructure.dependencies import get_update_entry_handler from app.context.entry.interface.schemas import UpdateEntryRequest, UpdateEntryResponse -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/entries") @@ -18,11 +15,13 @@ async def update_entry( entry_id: int, request: UpdateEntryRequest, - handler: Annotated[UpdateEntryHandlerContract, Depends(get_update_entry_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Update an entry (full update - all fields required)""" + logger = app_container.logger + handler = app_container.get_update_entry_handler() + logger.info("Update entry request", user_id=user_id, entry_id=entry_id) command = UpdateEntryCommand( diff --git a/app/context/household/infrastructure/dependencies.py b/app/context/household/infrastructure/dependencies.py deleted file mode 100644 index a9fe201..0000000 --- a/app/context/household/infrastructure/dependencies.py +++ /dev/null @@ -1,190 +0,0 @@ -from typing import Annotated - -from fastapi import Depends -from sqlalchemy.ext.asyncio import AsyncSession - -from app.context.household.application.contracts import ( - AcceptInviteHandlerContract, - CreateHouseholdHandlerContract, - DeclineInviteHandlerContract, - DeleteHouseholdHandlerContract, - GetHouseholdHandlerContract, - InviteUserHandlerContract, - ListHouseholdInvitesHandlerContract, - ListUserHouseholdsHandlerContract, - ListUserPendingInvitesHandlerContract, - RemoveMemberHandlerContract, - UpdateHouseholdHandlerContract, -) -from app.context.household.application.handlers import ( - AcceptInviteHandler, - CreateHouseholdHandler, - DeclineInviteHandler, - DeleteHouseholdHandler, - GetHouseholdHandler, - InviteUserHandler, - ListHouseholdInvitesHandler, - ListUserHouseholdsHandler, - ListUserPendingInvitesHandler, - RemoveMemberHandler, - UpdateHouseholdHandler, -) -from app.context.household.domain.contracts import ( - AcceptInviteServiceContract, - CreateHouseholdServiceContract, - DeclineInviteServiceContract, - HouseholdRepositoryContract, - InviteUserServiceContract, - RemoveMemberServiceContract, - RevokeInviteServiceContract, - UpdateHouseholdServiceContract, -) -from app.context.household.domain.services import ( - AcceptInviteService, - CreateHouseholdService, - DeclineInviteService, - InviteUserService, - RemoveMemberService, - RevokeInviteService, - UpdateHouseholdService, -) -from app.context.household.infrastructure.repositories import HouseholdRepository -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.database import get_db -from app.shared.infrastructure.dependencies import get_logger - - -# Repository dependencies -def get_household_repository( - db: Annotated[AsyncSession, Depends(get_db)], -) -> HouseholdRepositoryContract: - return HouseholdRepository(db) - - -# Service dependencies -def get_create_household_service( - household_repository: Annotated[HouseholdRepositoryContract, Depends(get_household_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> CreateHouseholdServiceContract: - return CreateHouseholdService(household_repository, logger) - - -def get_invite_user_service( - household_repository: Annotated[HouseholdRepositoryContract, Depends(get_household_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> InviteUserServiceContract: - return InviteUserService(household_repository, logger) - - -def get_accept_invite_service( - household_repository: Annotated[HouseholdRepositoryContract, Depends(get_household_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> AcceptInviteServiceContract: - return AcceptInviteService(household_repository, logger) - - -def get_decline_invite_service( - household_repository: Annotated[HouseholdRepositoryContract, Depends(get_household_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> DeclineInviteServiceContract: - return DeclineInviteService(household_repository, logger) - - -def get_remove_member_service( - household_repository: Annotated[HouseholdRepositoryContract, Depends(get_household_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> RemoveMemberServiceContract: - return RemoveMemberService(household_repository, logger) - - -def get_update_household_service( - household_repository: Annotated[HouseholdRepositoryContract, Depends(get_household_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> UpdateHouseholdServiceContract: - return UpdateHouseholdService(household_repository, logger) - - -def get_revoke_invite_service( - household_repository: Annotated[HouseholdRepositoryContract, Depends(get_household_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> RevokeInviteServiceContract: - return RevokeInviteService(household_repository, logger) - - -# Handler dependencies (Commands) -def get_create_household_handler( - service: Annotated[CreateHouseholdServiceContract, Depends(get_create_household_service)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> CreateHouseholdHandlerContract: - return CreateHouseholdHandler(service, logger) - - -def get_invite_user_handler( - service: Annotated[InviteUserServiceContract, Depends(get_invite_user_service)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> InviteUserHandlerContract: - return InviteUserHandler(service, logger) - - -def get_accept_invite_handler( - service: Annotated[AcceptInviteServiceContract, Depends(get_accept_invite_service)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> AcceptInviteHandlerContract: - return AcceptInviteHandler(service, logger) - - -def get_decline_invite_handler( - service: Annotated[DeclineInviteServiceContract, Depends(get_decline_invite_service)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> DeclineInviteHandlerContract: - return DeclineInviteHandler(service, logger) - - -def get_remove_member_handler( - service: Annotated[RemoveMemberServiceContract, Depends(get_remove_member_service)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> RemoveMemberHandlerContract: - return RemoveMemberHandler(service, logger) - - -def get_update_household_handler( - service: Annotated[UpdateHouseholdServiceContract, Depends(get_update_household_service)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> UpdateHouseholdHandlerContract: - return UpdateHouseholdHandler(service, logger) - - -def get_delete_household_handler( - repository: Annotated[HouseholdRepositoryContract, Depends(get_household_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> DeleteHouseholdHandlerContract: - return DeleteHouseholdHandler(repository, logger) - - -# Handler dependencies (Queries) -def get_list_household_invites_handler( - repository: Annotated[HouseholdRepositoryContract, Depends(get_household_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> ListHouseholdInvitesHandlerContract: - return ListHouseholdInvitesHandler(repository, logger) - - -def get_list_user_pending_invites_handler( - repository: Annotated[HouseholdRepositoryContract, Depends(get_household_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> ListUserPendingInvitesHandlerContract: - return ListUserPendingInvitesHandler(repository, logger) - - -def get_get_household_handler( - repository: Annotated[HouseholdRepositoryContract, Depends(get_household_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> GetHouseholdHandlerContract: - return GetHouseholdHandler(repository, logger) - - -def get_list_user_households_handler( - repository: Annotated[HouseholdRepositoryContract, Depends(get_household_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> ListUserHouseholdsHandlerContract: - return ListUserHouseholdsHandler(repository, logger) diff --git a/app/context/household/infrastructure/dependencies/__init__.py b/app/context/household/infrastructure/dependencies/__init__.py new file mode 100644 index 0000000..423e19b --- /dev/null +++ b/app/context/household/infrastructure/dependencies/__init__.py @@ -0,0 +1,27 @@ +from .dependencies import ( + accept_invite_handler_factory, + create_household_handler_factory, + decline_invite_hanlder_factory, + delete_household_hanlder_factory, + get_household_handler_factory, + invite_user_handler_factory, + list_household_invites_handler_factory, + list_user_households_handler_factory, + list_user_pending_invites_handler_factory, + remove_member_handler_factory, + update_household_handler_factory, +) + +__all__ = [ + "accept_invite_handler_factory", + "create_household_handler_factory", + "decline_invite_hanlder_factory", + "delete_household_hanlder_factory", + "get_household_handler_factory", + "invite_user_handler_factory", + "list_household_invites_handler_factory", + "list_user_households_handler_factory", + "list_user_pending_invites_handler_factory", + "remove_member_handler_factory", + "update_household_handler_factory", +] diff --git a/app/context/household/infrastructure/dependencies/dependencies.py b/app/context/household/infrastructure/dependencies/dependencies.py new file mode 100644 index 0000000..25a9b96 --- /dev/null +++ b/app/context/household/infrastructure/dependencies/dependencies.py @@ -0,0 +1,217 @@ +from sqlalchemy.ext.asyncio import AsyncSession + +from app.context.household.application.contracts import ( + AcceptInviteHandlerContract, + CreateHouseholdHandlerContract, + DeclineInviteHandlerContract, + DeleteHouseholdHandlerContract, + GetHouseholdHandlerContract, + InviteUserHandlerContract, + ListHouseholdInvitesHandlerContract, + ListUserHouseholdsHandlerContract, + ListUserPendingInvitesHandlerContract, + RemoveMemberHandlerContract, + UpdateHouseholdHandlerContract, +) +from app.context.household.domain.contracts import ( + AcceptInviteServiceContract, + CreateHouseholdServiceContract, + DeclineInviteServiceContract, + HouseholdRepositoryContract, + InviteUserServiceContract, + RemoveMemberServiceContract, + UpdateHouseholdServiceContract, +) +from app.shared.domain.contracts import LoggerContract + +# ───────────────────────────────────────────────────────────────── +# COMMAND HANDLERS (Write operations) +# ───────────────────────────────────────────────────────────────── + + +def accept_invite_handler_factory( + db: AsyncSession, + logger: LoggerContract, +) -> AcceptInviteHandlerContract: + from app.context.household.application.handlers import AcceptInviteHandler + + household_repository = _get_household_repository(db) + service = _get_accept_invite_service(household_repository, logger) + return AcceptInviteHandler(service, logger) + + +def create_household_handler_factory( + db: AsyncSession, + logger: LoggerContract, +) -> CreateHouseholdHandlerContract: + from app.context.household.application.handlers import CreateHouseholdHandler + + household_repo = _get_household_repository(db) + service = _get_create_household_service(household_repo, logger) + return CreateHouseholdHandler(service, logger) + + +def decline_invite_hanlder_factory( + db: AsyncSession, + logger: LoggerContract, +) -> DeclineInviteHandlerContract: + from app.context.household.application.handlers import DeclineInviteHandler + + household_repo = _get_household_repository(db) + decline_invite_service = _get_decline_invite_service(household_repo, logger) + return DeclineInviteHandler(decline_invite_service, logger) + + +def delete_household_hanlder_factory( + db: AsyncSession, + logger: LoggerContract, +) -> DeleteHouseholdHandlerContract: + from app.context.household.application.handlers import DeleteHouseholdHandler + + repository = _get_household_repository(db) + return DeleteHouseholdHandler(repository, logger) + + +def get_household_handler_factory( + db: AsyncSession, + logger: LoggerContract, +) -> GetHouseholdHandlerContract: + from app.context.household.application.handlers import GetHouseholdHandler + + repository = _get_household_repository(db) + return GetHouseholdHandler(repository, logger) + + +def invite_user_handler_factory( + db: AsyncSession, + logger: LoggerContract, +) -> InviteUserHandlerContract: + from app.context.household.application.handlers import InviteUserHandler + + household_repo = _get_household_repository(db) + service = _get_invite_user_service(household_repo, logger) + return InviteUserHandler(service, logger) + + +def remove_member_handler_factory( + db: AsyncSession, + logger: LoggerContract, +) -> RemoveMemberHandlerContract: + from app.context.household.application.handlers import RemoveMemberHandler + + household_repo = _get_household_repository(db) + service = _get_remove_member_service(household_repo, logger) + return RemoveMemberHandler(service, logger) + + +def update_household_handler_factory( + db: AsyncSession, + logger: LoggerContract, +) -> UpdateHouseholdHandlerContract: + from app.context.household.application.handlers import UpdateHouseholdHandler + + household_repo = _get_household_repository(db) + service = _get_update_household_service(household_repo, logger) + return UpdateHouseholdHandler(service, logger) + + +# ───────────────────────────────────────────────────────────────── +# QUERY HANDLERS (Read operations) +# ───────────────────────────────────────────────────────────────── + + +def list_household_invites_handler_factory( + db: AsyncSession, + logger: LoggerContract, +) -> ListHouseholdInvitesHandlerContract: + from app.context.household.application.handlers import ListHouseholdInvitesHandler + + repository = _get_household_repository(db) + return ListHouseholdInvitesHandler(repository, logger) + + +def list_user_households_handler_factory( + db: AsyncSession, + logger: LoggerContract, +) -> ListUserHouseholdsHandlerContract: + from app.context.household.application.handlers import ListUserHouseholdsHandler + + repository = _get_household_repository(db) + return ListUserHouseholdsHandler(repository, logger) + + +def list_user_pending_invites_handler_factory( + db: AsyncSession, + logger: LoggerContract, +) -> ListUserPendingInvitesHandlerContract: + from app.context.household.application.handlers import ListUserPendingInvitesHandler + + repository = _get_household_repository(db) + return ListUserPendingInvitesHandler(repository, logger) + + +# ───────────────────────────────────────────────────────────────── +# Private helper functions +# ───────────────────────────────────────────────────────────────── + + +def _get_accept_invite_service( + household_repository: HouseholdRepositoryContract, + logger: LoggerContract, +) -> AcceptInviteServiceContract: + from app.context.household.domain.services import AcceptInviteService + + return AcceptInviteService(household_repository, logger) + + +def _get_household_repository( + db: AsyncSession, +) -> HouseholdRepositoryContract: + from app.context.household.infrastructure.repositories import HouseholdRepository + + return HouseholdRepository(db) + + +def _get_create_household_service( + household_repository: HouseholdRepositoryContract, + logger: LoggerContract, +) -> CreateHouseholdServiceContract: + from app.context.household.domain.services import CreateHouseholdService + + return CreateHouseholdService(household_repository, logger) + + +def _get_decline_invite_service( + household_repository: HouseholdRepositoryContract, + logger: LoggerContract, +) -> DeclineInviteServiceContract: + from app.context.household.domain.services import DeclineInviteService + + return DeclineInviteService(household_repository, logger) + + +def _get_invite_user_service( + household_repository: HouseholdRepositoryContract, + logger: LoggerContract, +) -> InviteUserServiceContract: + from app.context.household.domain.services import InviteUserService + + return InviteUserService(household_repository, logger) + + +def _get_remove_member_service( + household_repository: HouseholdRepositoryContract, + logger: LoggerContract, +) -> RemoveMemberServiceContract: + from app.context.household.domain.services import RemoveMemberService + + return RemoveMemberService(household_repository, logger) + + +def _get_update_household_service( + household_repository: HouseholdRepositoryContract, + logger: LoggerContract, +) -> UpdateHouseholdServiceContract: + from app.context.household.domain.services import UpdateHouseholdService + + return UpdateHouseholdService(household_repository, logger) diff --git a/app/context/household/infrastructure/repositories/household_repository.py b/app/context/household/infrastructure/repositories/household_repository.py index 1c599c9..f828e6b 100644 --- a/app/context/household/infrastructure/repositories/household_repository.py +++ b/app/context/household/infrastructure/repositories/household_repository.py @@ -195,9 +195,12 @@ async def list_user_households(self, user_id: HouseholdUserID) -> list[Household ) # Combine with UNION (automatically deduplicates) - combined_stmt = union(owner_stmt, member_stmt) + union_stmt = union(owner_stmt, member_stmt) - result = await self._db.execute(combined_stmt) + # Use from_statement to properly map union results to ORM entities + final_stmt = select(HouseholdModel).from_statement(union_stmt) + + result = await self._db.execute(final_stmt) households = result.scalars().all() return [HouseholdMapper.to_dto_or_fail(h) for h in households] diff --git a/app/context/household/interface/rest/controllers/accept_invite_controller.py b/app/context/household/interface/rest/controllers/accept_invite_controller.py index 3758ace..71adaf7 100644 --- a/app/context/household/interface/rest/controllers/accept_invite_controller.py +++ b/app/context/household/interface/rest/controllers/accept_invite_controller.py @@ -3,12 +3,9 @@ from fastapi import APIRouter, Depends, HTTPException from app.context.household.application.commands import AcceptInviteCommand -from app.context.household.application.contracts import AcceptInviteHandlerContract from app.context.household.application.dto import AcceptInviteErrorCode -from app.context.household.infrastructure.dependencies import get_accept_invite_handler from app.context.household.interface.schemas import AcceptInviteResponse -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter() @@ -17,11 +14,12 @@ @router.post("/{household_id}/invites/accept", status_code=200) async def accept_invite( household_id: int, - handler: Annotated[AcceptInviteHandlerContract, Depends(get_accept_invite_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ) -> AcceptInviteResponse: """Accept a household invitation""" + logger = app_container.logger + handler = app_container.get_accept_invite_handler() logger.info("Accept invite request", user_id=user_id, household_id=household_id) diff --git a/app/context/household/interface/rest/controllers/create_household_controller.py b/app/context/household/interface/rest/controllers/create_household_controller.py index 1bf1bf1..c3b544d 100644 --- a/app/context/household/interface/rest/controllers/create_household_controller.py +++ b/app/context/household/interface/rest/controllers/create_household_controller.py @@ -3,17 +3,12 @@ from fastapi import APIRouter, Depends, HTTPException from app.context.household.application.commands import CreateHouseholdCommand -from app.context.household.application.contracts import CreateHouseholdHandlerContract from app.context.household.application.dto import CreateHouseholdErrorCode -from app.context.household.infrastructure.dependencies import ( - get_create_household_handler, -) from app.context.household.interface.schemas import ( CreateHouseholdRequest, CreateHouseholdResponse, ) -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter() @@ -22,11 +17,12 @@ @router.post("/", status_code=201) async def create_household( request: CreateHouseholdRequest, - handler: Annotated[CreateHouseholdHandlerContract, Depends(get_create_household_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ) -> CreateHouseholdResponse: """Create a new household""" + logger = app_container.logger + handler = app_container.get_create_household_handler() logger.info("Create household request", user_id=user_id, name=request.name) diff --git a/app/context/household/interface/rest/controllers/decline_invite_controller.py b/app/context/household/interface/rest/controllers/decline_invite_controller.py index 0d22b6e..44fc08e 100644 --- a/app/context/household/interface/rest/controllers/decline_invite_controller.py +++ b/app/context/household/interface/rest/controllers/decline_invite_controller.py @@ -3,12 +3,9 @@ from fastapi import APIRouter, Depends, HTTPException from app.context.household.application.commands import DeclineInviteCommand -from app.context.household.application.contracts import DeclineInviteHandlerContract from app.context.household.application.dto import DeclineInviteErrorCode -from app.context.household.infrastructure.dependencies import get_decline_invite_handler from app.context.household.interface.schemas import DeclineInviteResponse -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter() @@ -17,11 +14,12 @@ @router.post("/{household_id}/invites/decline", status_code=200) async def decline_invite( household_id: int, - handler: Annotated[DeclineInviteHandlerContract, Depends(get_decline_invite_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ) -> DeclineInviteResponse: """Decline a household invitation""" + logger = app_container.logger + handler = app_container.get_decline_invite_handler() logger.info("Decline invite request", user_id=user_id, household_id=household_id) diff --git a/app/context/household/interface/rest/controllers/delete_household_controller.py b/app/context/household/interface/rest/controllers/delete_household_controller.py index 22d470d..39de0ae 100644 --- a/app/context/household/interface/rest/controllers/delete_household_controller.py +++ b/app/context/household/interface/rest/controllers/delete_household_controller.py @@ -3,11 +3,8 @@ from fastapi import APIRouter, Depends, HTTPException from app.context.household.application.commands import DeleteHouseholdCommand -from app.context.household.application.contracts import DeleteHouseholdHandlerContract from app.context.household.application.dto import DeleteHouseholdErrorCode -from app.context.household.infrastructure.dependencies import get_delete_household_handler -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter() @@ -16,11 +13,12 @@ @router.delete("/{household_id}", status_code=204) async def delete_household( household_id: int, - handler: Annotated[DeleteHouseholdHandlerContract, Depends(get_delete_household_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Soft delete household (owner only)""" + logger = app_container.logger + handler = app_container.get_delete_household_handler() logger.info("Delete household request", household_id=household_id, user_id=user_id) diff --git a/app/context/household/interface/rest/controllers/get_household_controller.py b/app/context/household/interface/rest/controllers/get_household_controller.py index 58d198f..bc25a9e 100644 --- a/app/context/household/interface/rest/controllers/get_household_controller.py +++ b/app/context/household/interface/rest/controllers/get_household_controller.py @@ -2,13 +2,10 @@ from fastapi import APIRouter, Depends, HTTPException -from app.context.household.application.contracts import GetHouseholdHandlerContract from app.context.household.application.dto import GetHouseholdErrorCode from app.context.household.application.queries import GetHouseholdQuery -from app.context.household.infrastructure.dependencies import get_get_household_handler from app.context.household.interface.schemas import HouseholdResponse -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter() @@ -17,11 +14,12 @@ @router.get("/{household_id}", response_model=HouseholdResponse) async def get_household( household_id: int, - handler: Annotated[GetHouseholdHandlerContract, Depends(get_get_household_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Get household by ID (owner or active member only)""" + logger = app_container.logger + handler = app_container.get_household_handler() logger.info("Get household request", household_id=household_id, user_id=user_id) diff --git a/app/context/household/interface/rest/controllers/invite_user_controller.py b/app/context/household/interface/rest/controllers/invite_user_controller.py index 693a750..c1c7f0a 100644 --- a/app/context/household/interface/rest/controllers/invite_user_controller.py +++ b/app/context/household/interface/rest/controllers/invite_user_controller.py @@ -3,15 +3,12 @@ from fastapi import APIRouter, Depends, HTTPException from app.context.household.application.commands import InviteUserCommand -from app.context.household.application.contracts import InviteUserHandlerContract from app.context.household.application.dto import InviteUserErrorCode -from app.context.household.infrastructure.dependencies import get_invite_user_handler from app.context.household.interface.schemas import ( InviteUserRequest, InviteUserResponse, ) -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter() @@ -21,11 +18,12 @@ async def invite_user( household_id: int, request: InviteUserRequest, - handler: Annotated[InviteUserHandlerContract, Depends(get_invite_user_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ) -> InviteUserResponse: """Invite a user to a household""" + logger = app_container.logger + handler = app_container.get_invite_user_handler() logger.info( "Invite user request", diff --git a/app/context/household/interface/rest/controllers/list_household_invites_controller.py b/app/context/household/interface/rest/controllers/list_household_invites_controller.py index 6c93e71..a3f166a 100644 --- a/app/context/household/interface/rest/controllers/list_household_invites_controller.py +++ b/app/context/household/interface/rest/controllers/list_household_invites_controller.py @@ -2,16 +2,9 @@ from fastapi import APIRouter, Depends -from app.context.household.application.contracts import ( - ListHouseholdInvitesHandlerContract, -) from app.context.household.application.queries import ListHouseholdInvitesQuery -from app.context.household.infrastructure.dependencies import ( - get_list_household_invites_handler, -) from app.context.household.interface.schemas import HouseholdMemberResponse -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter() @@ -20,11 +13,12 @@ @router.get("/{household_id}/invites", status_code=200) async def list_household_invites( household_id: int, - handler: Annotated[ListHouseholdInvitesHandlerContract, Depends(get_list_household_invites_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ) -> list[HouseholdMemberResponse]: """List pending invitations for a household""" + logger = app_container.logger + handler = app_container.get_list_household_invites_handler() logger.info("List household invites request", household_id=household_id, user_id=user_id) diff --git a/app/context/household/interface/rest/controllers/list_user_households_controller.py b/app/context/household/interface/rest/controllers/list_user_households_controller.py index 89c401c..2a3da4d 100644 --- a/app/context/household/interface/rest/controllers/list_user_households_controller.py +++ b/app/context/household/interface/rest/controllers/list_user_households_controller.py @@ -2,14 +2,9 @@ from fastapi import APIRouter, Depends, HTTPException -from app.context.household.application.contracts import ListUserHouseholdsHandlerContract from app.context.household.application.queries import ListUserHouseholdsQuery -from app.context.household.infrastructure.dependencies import ( - get_list_user_households_handler, -) from app.context.household.interface.schemas import HouseholdResponse, ListHouseholdsResponse -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter() @@ -17,12 +12,14 @@ @router.get("/", response_model=ListHouseholdsResponse) async def list_user_households( - handler: Annotated[ListUserHouseholdsHandlerContract, Depends(get_list_user_households_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """List all households for the authenticated user""" + logger = app_container.logger + handler = app_container.get_list_user_households_handler() + logger.info("List user households request", user_id=user_id) query = ListUserHouseholdsQuery(user_id=user_id) diff --git a/app/context/household/interface/rest/controllers/list_user_pending_invites_controller.py b/app/context/household/interface/rest/controllers/list_user_pending_invites_controller.py index afb6565..56190f6 100644 --- a/app/context/household/interface/rest/controllers/list_user_pending_invites_controller.py +++ b/app/context/household/interface/rest/controllers/list_user_pending_invites_controller.py @@ -2,16 +2,9 @@ from fastapi import APIRouter, Depends -from app.context.household.application.contracts import ( - ListUserPendingInvitesHandlerContract, -) from app.context.household.application.queries import ListUserPendingInvitesQuery -from app.context.household.infrastructure.dependencies import ( - get_list_user_pending_invites_handler, -) from app.context.household.interface.schemas import HouseholdMemberResponse -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter() @@ -19,11 +12,12 @@ @router.get("/invites/pending", status_code=200) async def list_user_pending_invites( - handler: Annotated[ListUserPendingInvitesHandlerContract, Depends(get_list_user_pending_invites_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ) -> list[HouseholdMemberResponse]: """List all pending invitations for the authenticated user""" + logger = app_container.logger + handler = app_container.get_list_user_pending_invites_handler() logger.info("List user pending invites request", user_id=user_id) diff --git a/app/context/household/interface/rest/controllers/remove_member_controller.py b/app/context/household/interface/rest/controllers/remove_member_controller.py index 2d3cb9b..a5f32d5 100644 --- a/app/context/household/interface/rest/controllers/remove_member_controller.py +++ b/app/context/household/interface/rest/controllers/remove_member_controller.py @@ -3,12 +3,9 @@ from fastapi import APIRouter, Depends, HTTPException from app.context.household.application.commands import RemoveMemberCommand -from app.context.household.application.contracts import RemoveMemberHandlerContract from app.context.household.application.dto import RemoveMemberErrorCode -from app.context.household.infrastructure.dependencies import get_remove_member_handler from app.context.household.interface.schemas import RemoveMemberResponse -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter() @@ -18,11 +15,12 @@ async def remove_member( household_id: int, member_user_id: int, - handler: Annotated[RemoveMemberHandlerContract, Depends(get_remove_member_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ) -> RemoveMemberResponse: """Remove a member from a household""" + logger = app_container.logger + handler = app_container.get_remove_member_handler() logger.info( "Remove member request", diff --git a/app/context/household/interface/rest/controllers/update_household_controller.py b/app/context/household/interface/rest/controllers/update_household_controller.py index 2e8c7eb..4555b26 100644 --- a/app/context/household/interface/rest/controllers/update_household_controller.py +++ b/app/context/household/interface/rest/controllers/update_household_controller.py @@ -3,12 +3,9 @@ from fastapi import APIRouter, Depends, HTTPException from app.context.household.application.commands import UpdateHouseholdCommand -from app.context.household.application.contracts import UpdateHouseholdHandlerContract from app.context.household.application.dto import UpdateHouseholdErrorCode -from app.context.household.infrastructure.dependencies import get_update_household_handler from app.context.household.interface.schemas import HouseholdResponse, UpdateHouseholdRequest -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter() @@ -18,11 +15,12 @@ async def update_household( household_id: int, request: UpdateHouseholdRequest, - handler: Annotated[UpdateHouseholdHandlerContract, Depends(get_update_household_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Update household name (owner only)""" + logger = app_container.logger + handler = app_container.get_update_household_handler() logger.info("Update household request", household_id=household_id, user_id=user_id, name=request.name) diff --git a/app/context/reminder/infrastructure/__init__.py b/app/context/reminder/infrastructure/__init__.py index b0c7f13..45652ab 100644 --- a/app/context/reminder/infrastructure/__init__.py +++ b/app/context/reminder/infrastructure/__init__.py @@ -1,9 +1 @@ -from app.context.reminder.infrastructure.dependencies import ( - get_reminder_occurrence_repository, - get_reminder_repository, -) - -__all__ = [ - "get_reminder_repository", - "get_reminder_occurrence_repository", -] +# Infrastructure layer exports diff --git a/app/context/reminder/infrastructure/dependencies/__init__.py b/app/context/reminder/infrastructure/dependencies/__init__.py new file mode 100644 index 0000000..4ddc911 --- /dev/null +++ b/app/context/reminder/infrastructure/dependencies/__init__.py @@ -0,0 +1,17 @@ +from .dependencies import ( + get_create_reminder_handler, + get_delete_reminder_handler, + get_find_reminder_handler, + get_list_occurrences_handler, + get_list_reminders_handler, + get_update_reminder_handler, +) + +__all__ = [ + "get_create_reminder_handler", + "get_delete_reminder_handler", + "get_find_reminder_handler", + "get_list_occurrences_handler", + "get_list_reminders_handler", + "get_update_reminder_handler", +] diff --git a/app/context/reminder/infrastructure/dependencies.py b/app/context/reminder/infrastructure/dependencies/dependencies.py similarity index 52% rename from app/context/reminder/infrastructure/dependencies.py rename to app/context/reminder/infrastructure/dependencies/dependencies.py index 15055bc..bd0e699 100644 --- a/app/context/reminder/infrastructure/dependencies.py +++ b/app/context/reminder/infrastructure/dependencies/dependencies.py @@ -1,6 +1,3 @@ -from typing import Annotated - -from fastapi import Depends from sqlalchemy.ext.asyncio import AsyncSession from app.context.reminder.application.contracts import ( @@ -21,136 +18,160 @@ GenerateOccurrencesServiceContract, UpdateReminderServiceContract, ) -from app.shared.infrastructure.database import get_db - -# ============================================================================ -# Repository Dependencies -# ============================================================================ - - -def get_reminder_repository( - db: Annotated[AsyncSession, Depends(get_db)], -) -> ReminderRepositoryContract: - """ReminderRepository dependency injection""" - from app.context.reminder.infrastructure.repositories.reminder_repository import ReminderRepository - - return ReminderRepository(db) +from app.shared.domain.contracts import LoggerContract - -def get_reminder_occurrence_repository( - db: Annotated[AsyncSession, Depends(get_db)], -) -> ReminderOccurrenceRepositoryContract: - """ReminderOccurrenceRepository dependency injection""" - from app.context.reminder.infrastructure.repositories.reminder_occurrence_repository import ( - ReminderOccurrenceRepository, - ) - - return ReminderOccurrenceRepository(db) - - -# ============================================================================ -# Service Dependencies -# ============================================================================ - - -def get_generate_occurrences_service( - occurrence_repo: Annotated[ReminderOccurrenceRepositoryContract, Depends(get_reminder_occurrence_repository)], -) -> GenerateOccurrencesServiceContract: - """GenerateOccurrencesService dependency injection""" - from app.context.reminder.domain.services import GenerateOccurrencesService - - return GenerateOccurrencesService(occurrence_repo) - - -def get_create_reminder_service( - reminder_repo: Annotated[ReminderRepositoryContract, Depends(get_reminder_repository)], - generate_occurrences_service: Annotated[ - GenerateOccurrencesServiceContract, Depends(get_generate_occurrences_service) - ], -) -> CreateReminderServiceContract: - """CreateReminderService dependency injection""" - from app.context.reminder.domain.services import CreateReminderService - - return CreateReminderService(reminder_repo, generate_occurrences_service) - - -def get_update_reminder_service( - reminder_repo: Annotated[ReminderRepositoryContract, Depends(get_reminder_repository)], - occurrence_repo: Annotated[ReminderOccurrenceRepositoryContract, Depends(get_reminder_occurrence_repository)], - generate_occurrences_service: Annotated[ - GenerateOccurrencesServiceContract, Depends(get_generate_occurrences_service) - ], -) -> UpdateReminderServiceContract: - """UpdateReminderService dependency injection""" - from app.context.reminder.domain.services import UpdateReminderService - - return UpdateReminderService(reminder_repo, occurrence_repo, generate_occurrences_service) - - -def get_delete_reminder_service( - reminder_repo: Annotated[ReminderRepositoryContract, Depends(get_reminder_repository)], - occurrence_repo: Annotated[ReminderOccurrenceRepositoryContract, Depends(get_reminder_occurrence_repository)], -) -> DeleteReminderServiceContract: - """DeleteReminderService dependency injection""" - from app.context.reminder.domain.services import DeleteReminderService - - return DeleteReminderService(reminder_repo, occurrence_repo) - - -# ============================================================================ -# Handler Dependencies -# ============================================================================ +# ───────────────────────────────────────────────────────────────── +# COMMAND HANDLERS (Write operations) +# ───────────────────────────────────────────────────────────────── def get_create_reminder_handler( - service: Annotated[CreateReminderServiceContract, Depends(get_create_reminder_service)], + db: AsyncSession, + logger: LoggerContract, ) -> CreateReminderHandlerContract: """CreateReminderHandler dependency injection""" from app.context.reminder.application.handlers import CreateReminderHandler + reminder_repo = _get_reminder_repository(db) + reminder_occurrence_repo = _get_reminder_occurrence_repository(db) + + generate_occurrences_service = _get_generate_occurrences_service(reminder_occurrence_repo) + service = _get_create_reminder_service(reminder_repo, generate_occurrences_service) + return CreateReminderHandler(service) def get_update_reminder_handler( - service: Annotated[UpdateReminderServiceContract, Depends(get_update_reminder_service)], + db: AsyncSession, + logger: LoggerContract, ) -> UpdateReminderHandlerContract: """UpdateReminderHandler dependency injection""" from app.context.reminder.application.handlers import UpdateReminderHandler + reminder_repo = _get_reminder_repository(db) + reminder_occurrence_repo = _get_reminder_occurrence_repository(db) + gen_occurrence_service = _get_generate_occurrences_service(reminder_occurrence_repo) + + service = _get_update_reminder_service(reminder_repo, reminder_occurrence_repo, gen_occurrence_service) + return UpdateReminderHandler(service) def get_delete_reminder_handler( - service: Annotated[DeleteReminderServiceContract, Depends(get_delete_reminder_service)], + db: AsyncSession, + logger: LoggerContract, ) -> DeleteReminderHandlerContract: """DeleteReminderHandler dependency injection""" from app.context.reminder.application.handlers import DeleteReminderHandler + reminder_repo = _get_reminder_repository(db) + reminder_occurrence_repo = _get_reminder_occurrence_repository(db) + service = _get_delete_reminder_service(reminder_repo, reminder_occurrence_repo) + return DeleteReminderHandler(service) +# ───────────────────────────────────────────────────────────────── +# QUERY HANDLERS (Read operations) +# ───────────────────────────────────────────────────────────────── + + def get_find_reminder_handler( - repository: Annotated[ReminderRepositoryContract, Depends(get_reminder_repository)], + db: AsyncSession, + logger: LoggerContract, ) -> FindReminderHandlerContract: """FindReminderHandler dependency injection""" from app.context.reminder.application.handlers import FindReminderHandler + repository = _get_reminder_repository(db) + return FindReminderHandler(repository) def get_list_reminders_handler( - repository: Annotated[ReminderRepositoryContract, Depends(get_reminder_repository)], + db: AsyncSession, + logger: LoggerContract, ) -> ListRemindersHandlerContract: """ListRemindersHandler dependency injection""" from app.context.reminder.application.handlers import ListRemindersHandler + repository = _get_reminder_repository(db) + return ListRemindersHandler(repository) def get_list_occurrences_handler( - repository: Annotated[ReminderOccurrenceRepositoryContract, Depends(get_reminder_occurrence_repository)], + db: AsyncSession, + logger: LoggerContract, ) -> ListOccurrencesHandlerContract: """ListOccurrencesHandler dependency injection""" from app.context.reminder.application.handlers import ListOccurrencesHandler + repository = _get_reminder_occurrence_repository(db) + return ListOccurrencesHandler(repository) + + +# ───────────────────────────────────────────────────────────────── +# Private helper functions +# ───────────────────────────────────────────────────────────────── + + +def _get_reminder_repository( + db: AsyncSession, +) -> ReminderRepositoryContract: + """ReminderRepository dependency injection""" + from app.context.reminder.infrastructure.repositories.reminder_repository import ReminderRepository + + return ReminderRepository(db) + + +def _get_reminder_occurrence_repository( + db: AsyncSession, +) -> ReminderOccurrenceRepositoryContract: + """ReminderOccurrenceRepository dependency injection""" + from app.context.reminder.infrastructure.repositories.reminder_occurrence_repository import ( + ReminderOccurrenceRepository, + ) + + return ReminderOccurrenceRepository(db) + + +def _get_generate_occurrences_service( + occurrence_repo: ReminderOccurrenceRepositoryContract, +) -> GenerateOccurrencesServiceContract: + """GenerateOccurrencesService dependency injection""" + from app.context.reminder.domain.services import GenerateOccurrencesService + + return GenerateOccurrencesService(occurrence_repo) + + +def _get_create_reminder_service( + reminder_repo: ReminderRepositoryContract, + generate_occurrences_service: GenerateOccurrencesServiceContract, +) -> CreateReminderServiceContract: + """CreateReminderService dependency injection""" + from app.context.reminder.domain.services import CreateReminderService + + return CreateReminderService(reminder_repo, generate_occurrences_service) + + +def _get_update_reminder_service( + reminder_repo: ReminderRepositoryContract, + occurrence_repo: ReminderOccurrenceRepositoryContract, + generate_occurrences_service: GenerateOccurrencesServiceContract, +) -> UpdateReminderServiceContract: + """UpdateReminderService dependency injection""" + from app.context.reminder.domain.services import UpdateReminderService + + return UpdateReminderService(reminder_repo, occurrence_repo, generate_occurrences_service) + + +def _get_delete_reminder_service( + reminder_repo: ReminderRepositoryContract, + occurrence_repo: ReminderOccurrenceRepositoryContract, +) -> DeleteReminderServiceContract: + """DeleteReminderService dependency injection""" + from app.context.reminder.domain.services import DeleteReminderService + + return DeleteReminderService(reminder_repo, occurrence_repo) diff --git a/app/context/reminder/interface/rest/controllers/occurrence_controller.py b/app/context/reminder/interface/rest/controllers/occurrence_controller.py index fd33e8b..52d2a6c 100644 --- a/app/context/reminder/interface/rest/controllers/occurrence_controller.py +++ b/app/context/reminder/interface/rest/controllers/occurrence_controller.py @@ -4,11 +4,10 @@ from fastapi import APIRouter, Depends, HTTPException -from app.context.reminder.application.contracts import ListOccurrencesHandlerContract from app.context.reminder.application.dto import ListOccurrencesErrorCode from app.context.reminder.application.queries import ListOccurrencesQuery -from app.context.reminder.infrastructure.dependencies import get_list_occurrences_handler from app.context.reminder.interface.rest.schemas import OccurrenceListResponse, OccurrenceResponse +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/occurrences", tags=["occurrences"]) @@ -16,7 +15,7 @@ @router.get("", response_model=OccurrenceListResponse) async def list_occurrences( - handler: Annotated[ListOccurrencesHandlerContract, Depends(get_list_occurrences_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], reminder_id: int | None = None, ): @@ -26,6 +25,7 @@ async def list_occurrences( If reminder_id is provided, returns occurrences for that specific reminder. Otherwise, returns all pending occurrences for the user. """ + handler = app_container.get_list_occurrences_handler() query = ListOccurrencesQuery(user_id=user_id, reminder_id=reminder_id) diff --git a/app/context/reminder/interface/rest/controllers/reminder_controller.py b/app/context/reminder/interface/rest/controllers/reminder_controller.py index 5767640..b8b045e 100644 --- a/app/context/reminder/interface/rest/controllers/reminder_controller.py +++ b/app/context/reminder/interface/rest/controllers/reminder_controller.py @@ -9,13 +9,6 @@ DeleteReminderCommand, UpdateReminderCommand, ) -from app.context.reminder.application.contracts import ( - CreateReminderHandlerContract, - DeleteReminderHandlerContract, - FindReminderHandlerContract, - ListRemindersHandlerContract, - UpdateReminderHandlerContract, -) from app.context.reminder.application.dto import ( CreateReminderErrorCode, DeleteReminderErrorCode, @@ -24,13 +17,6 @@ UpdateReminderErrorCode, ) from app.context.reminder.application.queries import FindReminderQuery, ListRemindersQuery -from app.context.reminder.infrastructure.dependencies import ( - get_create_reminder_handler, - get_delete_reminder_handler, - get_find_reminder_handler, - get_list_reminders_handler, - get_update_reminder_handler, -) from app.context.reminder.interface.rest.schemas import ( CreateReminderRequest, DeleteReminderResponse, @@ -38,6 +24,7 @@ ReminderResponse, UpdateReminderRequest, ) +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/reminders", tags=["reminders"]) @@ -46,10 +33,11 @@ @router.post("", status_code=201, response_model=ReminderResponse) async def create_reminder( request: CreateReminderRequest, - handler: Annotated[CreateReminderHandlerContract, Depends(get_create_reminder_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], ): """Create a new reminder""" + handler = app_container.get_create_reminder_handler() command = CreateReminderCommand( user_id=user_id, @@ -90,11 +78,12 @@ async def create_reminder( @router.get("", response_model=ReminderListResponse) async def list_reminders( - handler: Annotated[ListRemindersHandlerContract, Depends(get_list_reminders_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], active_only: bool = True, ): """List all reminders for the authenticated user""" + handler = app_container.get_list_reminders_handler() query = ListRemindersQuery(user_id=user_id, active_only=active_only) @@ -129,10 +118,11 @@ async def list_reminders( @router.get("/{reminder_id}", response_model=ReminderResponse) async def get_reminder( reminder_id: int, - handler: Annotated[FindReminderHandlerContract, Depends(get_find_reminder_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], ): """Get a specific reminder by ID""" + handler = app_container.get_find_reminder_handler() query = FindReminderQuery(reminder_id=reminder_id, user_id=user_id) @@ -165,10 +155,11 @@ async def get_reminder( async def update_reminder( reminder_id: int, request: UpdateReminderRequest, - handler: Annotated[UpdateReminderHandlerContract, Depends(get_update_reminder_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], ): """Update an existing reminder""" + handler = app_container.get_update_reminder_handler() command = UpdateReminderCommand( reminder_id=reminder_id, @@ -213,10 +204,11 @@ async def update_reminder( @router.delete("/{reminder_id}", response_model=DeleteReminderResponse) async def delete_reminder( reminder_id: int, - handler: Annotated[DeleteReminderHandlerContract, Depends(get_delete_reminder_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], ): """Delete a reminder""" + handler = app_container.get_delete_reminder_handler() command = DeleteReminderCommand(reminder_id=reminder_id, user_id=user_id) diff --git a/app/context/user/infrastructure/dependencies.py b/app/context/user/infrastructure/dependencies.py deleted file mode 100644 index 7ffd936..0000000 --- a/app/context/user/infrastructure/dependencies.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Annotated - -from fastapi import Depends -from sqlalchemy.ext.asyncio import AsyncSession - -from app.context.user.application.contracts import FindUserHandlerContract -from app.context.user.application.handlers import FindUserHandler -from app.context.user.domain.contracts.infrastructure import UserRepositoryContract -from app.context.user.infrastructure.repositories import UserRepository -from app.shared.infrastructure.database import get_db - - -def get_user_repository(db: Annotated[AsyncSession, Depends(get_db)]) -> UserRepositoryContract: - """ - Initialize user repository - """ - return UserRepository(db) - - -def get_find_user_query_handler( - user_repo: Annotated[UserRepositoryContract, Depends(get_user_repository)], -) -> FindUserHandlerContract: - """ - Initialize FindUserHandler - """ - return FindUserHandler(user_repo) diff --git a/app/context/user/infrastructure/dependencies/__init__.py b/app/context/user/infrastructure/dependencies/__init__.py new file mode 100644 index 0000000..72c64c7 --- /dev/null +++ b/app/context/user/infrastructure/dependencies/__init__.py @@ -0,0 +1,3 @@ +from .dependencies import find_user_handler_factory + +__all__ = ["find_user_handler_factory"] diff --git a/app/context/user/infrastructure/dependencies/dependencies.py b/app/context/user/infrastructure/dependencies/dependencies.py new file mode 100644 index 0000000..622809c --- /dev/null +++ b/app/context/user/infrastructure/dependencies/dependencies.py @@ -0,0 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + +from app.context.user.application.contracts import FindUserHandlerContract +from app.context.user.application.handlers import FindUserHandler +from app.context.user.domain.contracts.infrastructure import UserRepositoryContract +from app.context.user.infrastructure.repositories import UserRepository +from app.shared.domain.contracts import LoggerContract + + +def find_user_handler_factory(db: AsyncSession, logger: LoggerContract) -> FindUserHandlerContract: + return FindUserHandler(_get_user_repository(db)) + + +def _get_user_repository(db: AsyncSession) -> UserRepositoryContract: + return UserRepository(db) diff --git a/app/context/user_account/infrastructure/dependencies/__init__.py b/app/context/user_account/infrastructure/dependencies/__init__.py new file mode 100644 index 0000000..d853985 --- /dev/null +++ b/app/context/user_account/infrastructure/dependencies/__init__.py @@ -0,0 +1,15 @@ +from .dependencies import ( + create_account_handler_factory, + delete_account_handler_factory, + find_account_by_id_handler_factory, + find_accounts_by_user_handler_factory, + update_account_handler_factory, +) + +__all__ = [ + "create_account_handler_factory", + "delete_account_handler_factory", + "find_account_by_id_handler_factory", + "find_accounts_by_user_handler_factory", + "update_account_handler_factory", +] diff --git a/app/context/user_account/infrastructure/dependencies.py b/app/context/user_account/infrastructure/dependencies/dependencies.py similarity index 63% rename from app/context/user_account/infrastructure/dependencies.py rename to app/context/user_account/infrastructure/dependencies/dependencies.py index 5f64f05..c8712d3 100644 --- a/app/context/user_account/infrastructure/dependencies.py +++ b/app/context/user_account/infrastructure/dependencies/dependencies.py @@ -1,6 +1,3 @@ -from typing import Annotated - -from fastapi import Depends from sqlalchemy.ext.asyncio import AsyncSession from app.context.user_account.application.contracts.create_account_handler_contract import ( @@ -28,83 +25,48 @@ UpdateAccountServiceContract, ) from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.database import get_db -from app.shared.infrastructure.dependencies import get_logger - - -def get_user_account_repository( - db: Annotated[AsyncSession, Depends(get_db)], -) -> UserAccountRepositoryContract: - """UserAccountRepository dependency injection""" - from app.context.user_account.infrastructure.repositories.user_account_repository import ( - UserAccountRepository, - ) - - return UserAccountRepository(db) - # ───────────────────────────────────────────────────────────────── # COMMAND HANDLERS (Write operations) # ───────────────────────────────────────────────────────────────── -def get_create_account_service( - account_repository: Annotated[UserAccountRepositoryContract, Depends(get_user_account_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> CreateAccountServiceContract: - """CreateAccountService dependency injection""" - from app.context.user_account.domain.services.create_account_service import ( - CreateAccountService, - ) - - return CreateAccountService(account_repository, logger) - - -def get_create_account_handler( - service: Annotated[CreateAccountServiceContract, Depends(get_create_account_service)], - logger: Annotated[LoggerContract, Depends(get_logger)], +def create_account_handler_factory( + db: AsyncSession, + logger: LoggerContract, ) -> CreateAccountHandlerContract: - """CreateAccountHandler dependency injection""" + """Factory for CreateAccountHandler with all dependencies""" from app.context.user_account.application.handlers.create_account_handler import ( CreateAccountHandler, ) + service = _get_create_account_service(_get_user_account_repository(db), logger) return CreateAccountHandler(service, logger) -def get_update_account_service( - repository: Annotated[UserAccountRepositoryContract, Depends(get_user_account_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], -) -> UpdateAccountServiceContract: - """UpdateAccountService dependency injection""" - from app.context.user_account.domain.services.update_account_service import ( - UpdateAccountService, - ) - - return UpdateAccountService(repository, logger) - - -def get_update_account_handler( - service: Annotated[UpdateAccountServiceContract, Depends(get_update_account_service)], - logger: Annotated[LoggerContract, Depends(get_logger)], +def update_account_handler_factory( + db: AsyncSession, + logger: LoggerContract, ) -> UpdateAccountHandlerContract: - """UpdateAccountHandler dependency injection""" + """Factory for UpdateAccountHandler with all dependencies""" from app.context.user_account.application.handlers.update_account_handler import ( UpdateAccountHandler, ) + service = _get_update_account_service(_get_user_account_repository(db), logger) return UpdateAccountHandler(service, logger) -def get_delete_account_handler( - repository: Annotated[UserAccountRepositoryContract, Depends(get_user_account_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], +def delete_account_handler_factory( + db: AsyncSession, + logger: LoggerContract, ) -> DeleteAccountHandlerContract: - """DeleteAccountHandler dependency injection""" + """Factory for DeleteAccountHandler with all dependencies""" from app.context.user_account.application.handlers.delete_account_handler import ( DeleteAccountHandler, ) + repository = _get_user_account_repository(db) return DeleteAccountHandler(repository, logger) @@ -113,25 +75,65 @@ def get_delete_account_handler( # ───────────────────────────────────────────────────────────────── -def get_find_account_by_id_handler( - repository: Annotated[UserAccountRepositoryContract, Depends(get_user_account_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], +def find_account_by_id_handler_factory( + db: AsyncSession, + logger: LoggerContract, ) -> FindAccountByIdHandlerContract: - """FindAccountByIdHandler dependency injection""" + """Factory for FindAccountByIdHandler with all dependencies""" from app.context.user_account.application.handlers.find_account_by_id_handler import ( FindAccountByIdHandler, ) + repository = _get_user_account_repository(db) return FindAccountByIdHandler(repository, logger) -def get_find_accounts_by_user_handler( - repository: Annotated[UserAccountRepositoryContract, Depends(get_user_account_repository)], - logger: Annotated[LoggerContract, Depends(get_logger)], +def find_accounts_by_user_handler_factory( + db: AsyncSession, + logger: LoggerContract, ) -> FindAccountsByUserHandlerContract: - """FindAccountsByUserHandler dependency injection""" + """Factory for FindAccountsByUserHandler with all dependencies""" from app.context.user_account.application.handlers.find_accounts_by_user_handler import ( FindAccountsByUserHandler, ) + repository = _get_user_account_repository(db) return FindAccountsByUserHandler(repository, logger) + + +# ───────────────────────────────────────────────────────────────── +# Private helper functions +# ───────────────────────────────────────────────────────────────── + + +def _get_user_account_repository(db: AsyncSession) -> UserAccountRepositoryContract: + """Get user account repository instance""" + from app.context.user_account.infrastructure.repositories.user_account_repository import ( + UserAccountRepository, + ) + + return UserAccountRepository(db) + + +def _get_create_account_service( + repository: UserAccountRepositoryContract, + logger: LoggerContract, +) -> CreateAccountServiceContract: + """Get create account service instance""" + from app.context.user_account.domain.services.create_account_service import ( + CreateAccountService, + ) + + return CreateAccountService(repository, logger) + + +def _get_update_account_service( + repository: UserAccountRepositoryContract, + logger: LoggerContract, +) -> UpdateAccountServiceContract: + """Get update account service instance""" + from app.context.user_account.domain.services.update_account_service import ( + UpdateAccountService, + ) + + return UpdateAccountService(repository, logger) diff --git a/app/context/user_account/interface/rest/controllers/create_account_controller.py b/app/context/user_account/interface/rest/controllers/create_account_controller.py index ef88ab0..e32645b 100644 --- a/app/context/user_account/interface/rest/controllers/create_account_controller.py +++ b/app/context/user_account/interface/rest/controllers/create_account_controller.py @@ -5,21 +5,14 @@ from app.context.user_account.application.commands import ( CreateAccountCommand, ) -from app.context.user_account.application.contracts import ( - CreateAccountHandlerContract, -) from app.context.user_account.application.dto import ( CreateAccountErrorCode, ) -from app.context.user_account.infrastructure.dependencies import ( - get_create_account_handler, -) from app.context.user_account.interface.schemas import ( CreateAccountRequest, CreateAccountResponse, ) -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/accounts", tags=["accounts"]) @@ -28,11 +21,13 @@ @router.post("", response_model=CreateAccountResponse, status_code=201) async def create_account( request: CreateAccountRequest, - handler: Annotated[CreateAccountHandlerContract, Depends(get_create_account_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Create a new user account""" + logger = app_container.logger + handler = app_container.get_create_account_handler() + logger.info("Account creation request", user_id=user_id, name=request.name, currency=request.currency) command = CreateAccountCommand( diff --git a/app/context/user_account/interface/rest/controllers/delete_account_controller.py b/app/context/user_account/interface/rest/controllers/delete_account_controller.py index 177eb5c..b9acd69 100644 --- a/app/context/user_account/interface/rest/controllers/delete_account_controller.py +++ b/app/context/user_account/interface/rest/controllers/delete_account_controller.py @@ -5,17 +5,10 @@ from app.context.user_account.application.commands import ( DeleteAccountCommand, ) -from app.context.user_account.application.contracts import ( - DeleteAccountHandlerContract, -) from app.context.user_account.application.dto import ( DeleteAccountErrorCode, ) -from app.context.user_account.infrastructure.dependencies import ( - get_delete_account_handler, -) -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/accounts", tags=["accounts"]) @@ -24,11 +17,13 @@ @router.delete("/{account_id}", status_code=204) async def delete_account( account_id: int, - handler: Annotated[DeleteAccountHandlerContract, Depends(get_delete_account_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Delete a user account (soft delete)""" + logger = app_container.logger + handler = app_container.get_delete_account_handler() + logger.info("Account deletion request", user_id=user_id, account_id=account_id) command = DeleteAccountCommand( diff --git a/app/context/user_account/interface/rest/controllers/find_account_controller.py b/app/context/user_account/interface/rest/controllers/find_account_controller.py index 86ee69f..147b7c0 100644 --- a/app/context/user_account/interface/rest/controllers/find_account_controller.py +++ b/app/context/user_account/interface/rest/controllers/find_account_controller.py @@ -2,12 +2,6 @@ from fastapi import APIRouter, Depends, HTTPException -from app.context.user_account.application.contracts.find_account_by_id_handler_contract import ( - FindAccountByIdHandlerContract, -) -from app.context.user_account.application.contracts.find_accounts_by_user_handler_contract import ( - FindAccountsByUserHandlerContract, -) from app.context.user_account.application.dto import ( FindMultipleAccountsErrorCode, FindSingleAccountErrorCode, @@ -18,13 +12,8 @@ from app.context.user_account.application.queries.find_accounts_by_user_query import ( FindAccountsByUserQuery, ) -from app.context.user_account.infrastructure.dependencies import ( - get_find_account_by_id_handler, - get_find_accounts_by_user_handler, -) from app.context.user_account.interface.schemas.account_response import AccountResponse -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/accounts", tags=["accounts"]) @@ -33,11 +22,13 @@ @router.get("/{account_id}", response_model=AccountResponse) async def get_account( account_id: int, - handler: Annotated[FindAccountByIdHandlerContract, Depends(get_find_account_by_id_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Get a specific user account by ID""" + logger = app_container.logger + handler = app_container.get_find_account_by_id_handler() + query = FindAccountByIdQuery( account_id=account_id, user_id=user_id, @@ -83,11 +74,13 @@ async def get_account( @router.get("", response_model=list[AccountResponse]) async def get_all_accounts( - handler: Annotated[FindAccountsByUserHandlerContract, Depends(get_find_accounts_by_user_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Get all accounts for the authenticated user""" + logger = app_container.logger + handler = app_container.get_find_accounts_by_user_handler() + query = FindAccountsByUserQuery(user_id=user_id) result = await handler.handle(query) diff --git a/app/context/user_account/interface/rest/controllers/update_account_controller.py b/app/context/user_account/interface/rest/controllers/update_account_controller.py index e277b40..ca7dd4b 100644 --- a/app/context/user_account/interface/rest/controllers/update_account_controller.py +++ b/app/context/user_account/interface/rest/controllers/update_account_controller.py @@ -5,21 +5,14 @@ from app.context.user_account.application.commands import ( UpdateAccountCommand, ) -from app.context.user_account.application.contracts import ( - UpdateAccountHandlerContract, -) from app.context.user_account.application.dto import ( UpdateAccountErrorCode, ) -from app.context.user_account.infrastructure.dependencies import ( - get_update_account_handler, -) from app.context.user_account.interface.schemas import ( UpdateAccountRequest, UpdateAccountResponse, ) -from app.shared.domain.contracts import LoggerContract -from app.shared.infrastructure.dependencies import get_logger +from app.shared.infrastructure.container import ApplicationContainer, get_fastapi_app_container from app.shared.infrastructure.middleware import get_current_user_id router = APIRouter(prefix="/accounts", tags=["accounts"]) @@ -29,11 +22,13 @@ async def update_account( account_id: int, request: UpdateAccountRequest, - handler: Annotated[UpdateAccountHandlerContract, Depends(get_update_account_handler)], + app_container: Annotated[ApplicationContainer, Depends(get_fastapi_app_container)], user_id: Annotated[int, Depends(get_current_user_id)], - logger: Annotated[LoggerContract, Depends(get_logger)], ): """Update a user account (full update - all fields required)""" + logger = app_container.logger + handler = app_container.get_update_account_handler() + logger.info("Account update request", user_id=user_id, account_id=account_id, name=request.name) command = UpdateAccountCommand( diff --git a/app/shared/infrastructure/container/__init__.py b/app/shared/infrastructure/container/__init__.py new file mode 100644 index 0000000..ba6287f --- /dev/null +++ b/app/shared/infrastructure/container/__init__.py @@ -0,0 +1,17 @@ +from typing import Annotated + +from fastapi import Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.shared.infrastructure.database import get_db + +from .app_container import ApplicationContainer + + +def get_fastapi_app_container( + db: Annotated[AsyncSession, Depends(get_db)], +) -> ApplicationContainer: + return ApplicationContainer(db) + + +__all__ = ["ApplicationContainer", "get_fastapi_app_container"] diff --git a/app/shared/infrastructure/container/app_container.py b/app/shared/infrastructure/container/app_container.py new file mode 100644 index 0000000..1102bf6 --- /dev/null +++ b/app/shared/infrastructure/container/app_container.py @@ -0,0 +1,280 @@ +from sqlalchemy.ext.asyncio import AsyncSession + +from app.context.household.application.contracts import ( + AcceptInviteHandlerContract, + CreateHouseholdHandlerContract, + DeclineInviteHandlerContract, + DeleteHouseholdHandlerContract, + GetHouseholdHandlerContract, + InviteUserHandlerContract, + ListHouseholdInvitesHandlerContract, + ListUserHouseholdsHandlerContract, + ListUserPendingInvitesHandlerContract, + RemoveMemberHandlerContract, + UpdateHouseholdHandlerContract, +) +from app.shared.domain.contracts import LoggerContract +from app.shared.infrastructure.dependencies import get_logger + + +class ApplicationContainer: + _db: AsyncSession + _logger: LoggerContract + + def __init__(self, db: AsyncSession): + self._db = db + self._logger = get_logger() + + @property + def db(self) -> AsyncSession: + """Get database session""" + return self._db + + @property + def logger(self) -> LoggerContract: + """Get logger instance""" + return self._logger + + # ========================================================================= + # Auth Context + # ========================================================================= + + def get_login_handler(self): + """Get login handler with all dependencies""" + from app.context.auth.infrastructure.dependencies import login_handler_factory + + return login_handler_factory(self.db, self.logger) + + # ========================================================================= + # User Account Context + # ========================================================================= + + def get_create_account_handler(self): + """Get create account handler with all dependencies""" + from app.context.user_account.infrastructure.dependencies import ( + create_account_handler_factory, + ) + + return create_account_handler_factory(self.db, self.logger) + + def get_update_account_handler(self): + """Get update account handler with all dependencies""" + from app.context.user_account.infrastructure.dependencies import ( + update_account_handler_factory, + ) + + return update_account_handler_factory(self.db, self.logger) + + def get_delete_account_handler(self): + """Get delete account handler with all dependencies""" + from app.context.user_account.infrastructure.dependencies import ( + delete_account_handler_factory, + ) + + return delete_account_handler_factory(self.db, self.logger) + + def get_find_account_by_id_handler(self): + """Get find account by id handler with all dependencies""" + from app.context.user_account.infrastructure.dependencies import ( + find_account_by_id_handler_factory, + ) + + return find_account_by_id_handler_factory(self.db, self.logger) + + def get_find_accounts_by_user_handler(self): + """Get find accounts by user handler with all dependencies""" + from app.context.user_account.infrastructure.dependencies import ( + find_accounts_by_user_handler_factory, + ) + + return find_accounts_by_user_handler_factory(self.db, self.logger) + + # ========================================================================= + # Credit Card Context + # ========================================================================= + + def get_create_credit_card_handler(self): + """Get create credit card handler with all dependencies""" + from app.context.credit_card.infrastructure.dependencies import ( + create_credit_card_handler_factory, + ) + + return create_credit_card_handler_factory(self.db, self.logger) + + def get_update_credit_card_handler(self): + """Get update credit card handler with all dependencies""" + from app.context.credit_card.infrastructure.dependencies import ( + update_credit_card_handler_factory, + ) + + return update_credit_card_handler_factory(self.db, self.logger) + + def get_delete_credit_card_handler(self): + """Get delete credit card handler with all dependencies""" + from app.context.credit_card.infrastructure.dependencies import ( + delete_credit_card_handler_factory, + ) + + return delete_credit_card_handler_factory(self.db, self.logger) + + def get_find_credit_card_by_id_handler(self): + """Get find credit card by id handler with all dependencies""" + from app.context.credit_card.infrastructure.dependencies import ( + find_credit_card_by_id_handler_factory, + ) + + return find_credit_card_by_id_handler_factory(self.db, self.logger) + + def get_find_credit_cards_by_user_handler(self): + """Get find credit cards by user handler with all dependencies""" + from app.context.credit_card.infrastructure.dependencies import ( + find_credit_cards_by_user_handler_factory, + ) + + return find_credit_cards_by_user_handler_factory(self.db, self.logger) + + # ========================================================================= + # Entry Context + # ========================================================================= + + def get_create_entry_handler(self): + """Get create entry handler with all dependencies""" + from app.context.entry.infrastructure.dependencies import create_entry_handler_factory + + return create_entry_handler_factory(self.db, self.logger) + + def get_update_entry_handler(self): + """Get update entry handler with all dependencies""" + from app.context.entry.infrastructure.dependencies import update_entry_handler_factory + + return update_entry_handler_factory(self.db, self.logger) + + def get_delete_entry_handler(self): + """Get delete entry handler with all dependencies""" + from app.context.entry.infrastructure.dependencies import delete_entry_handler_factory + + return delete_entry_handler_factory(self.db, self.logger) + + def get_find_entry_by_id_handler(self): + """Get find entry by id handler with all dependencies""" + from app.context.entry.infrastructure.dependencies import find_entry_by_id_handler_factory + + return find_entry_by_id_handler_factory(self.db, self.logger) + + def get_find_entries_by_account_month_handler(self): + """Get find entries by account and month handler with all dependencies""" + from app.context.entry.infrastructure.dependencies import ( + find_entries_by_account_month_handler_factory, + ) + + return find_entries_by_account_month_handler_factory(self.db, self.logger) + + # ========================================================================= + # Household Context + # ========================================================================= + def get_accept_invite_handler(self) -> AcceptInviteHandlerContract: + """Get accept invite handler with all dependencies""" + + from app.context.household.infrastructure.dependencies import accept_invite_handler_factory + + return accept_invite_handler_factory(self.db, self.logger) + + def get_create_household_handler(self) -> CreateHouseholdHandlerContract: + """Get create household handler""" + from app.context.household.infrastructure.dependencies import create_household_handler_factory + + return create_household_handler_factory(self.db, self.logger) + + def get_decline_invite_handler(self) -> DeclineInviteHandlerContract: + """Get decline invite household handler""" + from app.context.household.infrastructure.dependencies import decline_invite_hanlder_factory + + return decline_invite_hanlder_factory(self.db, self.logger) + + def get_delete_household_handler(self) -> DeleteHouseholdHandlerContract: + """Get delete household handler with all dependencies""" + from app.context.household.infrastructure.dependencies import delete_household_hanlder_factory + + return delete_household_hanlder_factory(self.db, self.logger) + + def get_household_handler(self) -> GetHouseholdHandlerContract: + """Get household handler iwth all dependencies""" + from app.context.household.infrastructure.dependencies import get_household_handler_factory + + return get_household_handler_factory(self.db, self.logger) + + def get_invite_user_handler(self) -> InviteUserHandlerContract: + """Get invite user handler with all dependencies""" + from app.context.household.infrastructure.dependencies import invite_user_handler_factory + + return invite_user_handler_factory(self.db, self.logger) + + def get_remove_member_handler(self) -> RemoveMemberHandlerContract: + """Get remove member handler with all dependencies""" + from app.context.household.infrastructure.dependencies import remove_member_handler_factory + + return remove_member_handler_factory(self.db, self.logger) + + def get_update_household_handler(self) -> UpdateHouseholdHandlerContract: + """Get update household handler with all dependencies""" + from app.context.household.infrastructure.dependencies import update_household_handler_factory + + return update_household_handler_factory(self.db, self.logger) + + def get_list_household_invites_handler(self) -> ListHouseholdInvitesHandlerContract: + """Get list household invites handler with all dependencies""" + from app.context.household.infrastructure.dependencies import list_household_invites_handler_factory + + return list_household_invites_handler_factory(self.db, self.logger) + + def get_list_user_households_handler(self) -> ListUserHouseholdsHandlerContract: + """Get list user households handler with all dependencies""" + from app.context.household.infrastructure.dependencies import list_user_households_handler_factory + + return list_user_households_handler_factory(self.db, self.logger) + + def get_list_user_pending_invites_handler(self) -> ListUserPendingInvitesHandlerContract: + """Get list user pending invites handler with all dependencies""" + from app.context.household.infrastructure.dependencies import list_user_pending_invites_handler_factory + + return list_user_pending_invites_handler_factory(self.db, self.logger) + + # ========================================================================= + # Reminder Context + # ========================================================================= + + def get_create_reminder_handler(self): + """Get create reminder handler with all dependencies""" + from app.context.reminder.infrastructure.dependencies import get_create_reminder_handler + + return get_create_reminder_handler(self.db, self.logger) + + def get_update_reminder_handler(self): + """Get update reminder handler with all dependencies""" + from app.context.reminder.infrastructure.dependencies import get_update_reminder_handler + + return get_update_reminder_handler(self.db, self.logger) + + def get_delete_reminder_handler(self): + """Get delete reminder handler with all dependencies""" + from app.context.reminder.infrastructure.dependencies import get_delete_reminder_handler + + return get_delete_reminder_handler(self.db, self.logger) + + def get_find_reminder_handler(self): + """Get find reminder handler with all dependencies""" + from app.context.reminder.infrastructure.dependencies import get_find_reminder_handler + + return get_find_reminder_handler(self.db, self.logger) + + def get_list_reminders_handler(self): + """Get list reminders handler with all dependencies""" + from app.context.reminder.infrastructure.dependencies import get_list_reminders_handler + + return get_list_reminders_handler(self.db, self.logger) + + def get_list_occurrences_handler(self): + """Get list occurrences handler with all dependencies""" + from app.context.reminder.infrastructure.dependencies import get_list_occurrences_handler + + return get_list_occurrences_handler(self.db, self.logger) diff --git a/justfile b/justfile index 95a50ee..7d543db 100644 --- a/justfile +++ b/justfile @@ -10,6 +10,10 @@ default: dev: uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8080 +# Start development server with debug environment +dev-debug: + APP_ENV=debug uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8080 + # Start development server with test environment dev-test: APP_ENV=test uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8080