Skip to content

Commit c876ab8

Browse files
committed
Apply di to user module
1 parent 439c905 commit c876ab8

11 files changed

Lines changed: 90 additions & 62 deletions

File tree

.pre-commit-config.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ repos:
3232
"types-redis==4.6.0",
3333
"types-ujson==5.9.0",
3434
]
35+
- repo: https://github.com/pycqa/isort
36+
rev: 5.13.2
37+
hooks:
38+
- id: isort
39+
name: isort (python)
3540
ci:
3641
autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
3742
autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate

app/container.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from dependency_injector.containers import DeclarativeContainer, WiringConfiguration
2+
from dependency_injector.providers import Factory, Singleton
3+
4+
from app.user.adapter.output.persistence.repository_adapter import UserRepositoryAdapter
5+
from app.user.adapter.output.persistence.sqlalchemy.user import UserSQLAlchemyRepo
6+
from app.user.application.service.user import UserService
7+
8+
9+
class Container(DeclarativeContainer):
10+
wiring_config = WiringConfiguration(packages=["app"])
11+
12+
user_repo = Singleton(UserSQLAlchemyRepo)
13+
user_repo_adapter = Factory(UserRepositoryAdapter, user_repo=user_repo)
14+
user_service = Factory(UserService, repository=user_repo_adapter)

app/server.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
1-
from fastapi import FastAPI, Request, Depends
1+
from fastapi import Depends, FastAPI, Request
22
from fastapi.middleware import Middleware
33
from fastapi.middleware.cors import CORSMiddleware
44
from fastapi.responses import JSONResponse
55

66
from app.auth.adapter.input.api import router as auth_router
7+
from app.container import Container
8+
from app.user.adapter.input.api import router as user_router
79
from core.config import config
810
from core.exceptions import CustomException
911
from core.fastapi.dependencies import Logging
1012
from core.fastapi.middlewares import (
11-
AuthenticationMiddleware,
1213
AuthBackend,
13-
SQLAlchemyMiddleware,
14+
AuthenticationMiddleware,
1415
ResponseLogMiddleware,
16+
SQLAlchemyMiddleware,
1517
)
16-
from core.helpers.cache import Cache, RedisBackend, CustomKeyMaker
17-
from app.user.adapter.input.api import router as user_router
18+
from core.helpers.cache import Cache, CustomKeyMaker, RedisBackend
1819

1920

2021
def init_routers(app_: FastAPI) -> None:
22+
container = Container()
23+
user_router.container = container
24+
auth_router.container = container
2125
app_.include_router(user_router)
2226
app_.include_router(auth_router)
2327

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1+
from dependency_injector.wiring import Provide, inject
12
from fastapi import APIRouter, Depends, Query
23

3-
from core.fastapi.dependencies import (
4-
PermissionDependency,
5-
IsAdmin,
6-
)
7-
from app.user.adapter.input.api.v1.request import LoginRequest, CreateUserRequest
4+
from app.container import Container
5+
from app.user.adapter.input.api.v1.request import CreateUserRequest, LoginRequest
86
from app.user.adapter.input.api.v1.response import LoginResponse
9-
from app.user.application.dto import CreateUserResponseDTO
10-
from app.user.application.dto import GetUserListResponseDTO
11-
from app.user.application.service.user import UserService
7+
from app.user.application.dto import CreateUserResponseDTO, GetUserListResponseDTO
128
from app.user.domain.command import CreateUserCommand
9+
from app.user.domain.usecase.user import UserUseCase
10+
from core.fastapi.dependencies import IsAdmin, PermissionDependency
1311

1412
user_router = APIRouter()
1513

@@ -19,27 +17,37 @@
1917
response_model=list[GetUserListResponseDTO],
2018
dependencies=[Depends(PermissionDependency([IsAdmin]))],
2119
)
20+
@inject
2221
async def get_user_list(
2322
limit: int = Query(10, description="Limit"),
2423
prev: int = Query(None, description="Prev ID"),
24+
usecase: UserUseCase = Depends(Provide[Container.user_service]),
2525
):
26-
return await UserService().get_user_list(limit=limit, prev=prev)
26+
return await usecase.get_user_list(limit=limit, prev=prev)
2727

2828

2929
@user_router.post(
3030
"",
3131
response_model=CreateUserResponseDTO,
3232
)
33-
async def create_user(request: CreateUserRequest):
33+
@inject
34+
async def create_user(
35+
request: CreateUserRequest,
36+
usecase: UserUseCase = Depends(Provide[Container.user_service]),
37+
):
3438
command = CreateUserCommand(**request.model_dump())
35-
await UserService().create_user(command=command)
39+
await usecase.create_user(command=command)
3640
return {"email": request.email, "nickname": request.nickname}
3741

3842

3943
@user_router.post(
4044
"/login",
4145
response_model=LoginResponse,
4246
)
43-
async def login(request: LoginRequest):
44-
token = await UserService().login(email=request.email, password=request.password)
47+
@inject
48+
async def login(
49+
request: LoginRequest,
50+
usecase: UserUseCase = Depends(Provide[Container.user_service]),
51+
):
52+
token = await usecase.login(email=request.email, password=request.password)
4553
return {"token": token.token, "refresh_token": token.refresh_token}

app/user/adapter/output/persistence/repository_adapter.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
from app.user.adapter.output.persistence.sqlalchemy.user import UserSQLAlchemyRepo
21
from app.user.domain.entity.user import User, UserRead
32
from app.user.domain.repository.user import UserRepo
43

54

65
class UserRepositoryAdapter:
7-
def __init__(self):
8-
self.user_repo: UserRepo = UserSQLAlchemyRepo()
6+
def __init__(self, *, user_repo: UserRepo):
7+
self.user_repo = user_repo
98

109
async def get_users(
1110
self,

app/user/application/service/user.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
from app.user.adapter.output.persistence.repository_adapter import UserRepositoryAdapter
2-
from app.user.application.dto import (
3-
LoginResponseDTO,
4-
)
2+
from app.user.application.dto import LoginResponseDTO
53
from app.user.application.exception import (
6-
PasswordDoesNotMatchException,
74
DuplicateEmailOrNicknameException,
5+
PasswordDoesNotMatchException,
86
UserNotFoundException,
97
)
108
from app.user.domain.command import CreateUserCommand
11-
from app.user.domain.entity.user import UserRead, User
9+
from app.user.domain.entity.user import User, UserRead
1210
from app.user.domain.usecase.user import UserUseCase
1311
from app.user.domain.vo.location import Location
1412
from core.db import Transactional
1513
from core.helpers.token import TokenHelper
1614

1715

1816
class UserService(UserUseCase):
19-
def __init__(self):
20-
self.repository = UserRepositoryAdapter()
17+
def __init__(self, *, repository: UserRepositoryAdapter):
18+
self.repository = repository
2119

2220
async def get_user_list(
2321
self,

core/db/mixins/timestamp_mixin.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
from sqlalchemy import Column, DateTime, func
2-
from sqlalchemy.ext.declarative import declared_attr
1+
from datetime import datetime
32

3+
from sqlalchemy import DateTime, func
4+
from sqlalchemy.orm import Mapped, mapped_column
45

5-
class TimestampMixin:
6-
@declared_attr
7-
def created_at(cls):
8-
return Column(DateTime, default=func.now(), nullable=False)
96

10-
@declared_attr
11-
def updated_at(cls):
12-
return Column(
13-
DateTime,
14-
default=func.now(),
15-
onupdate=func.now(),
16-
nullable=False,
17-
)
7+
class TimestampMixin:
8+
created_at: Mapped[datetime] = mapped_column(
9+
DateTime,
10+
default=func.now(),
11+
nullable=False,
12+
)
13+
updated_at: Mapped[datetime] = mapped_column(
14+
DateTime,
15+
default=func.now(),
16+
onupdate=func.now(),
17+
nullable=False,
18+
)

core/fastapi/dependencies/permission.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
from abc import ABC, abstractmethod
22
from typing import Type
33

4-
from fastapi import Request
4+
from dependency_injector.wiring import Provide, inject
5+
from fastapi import Depends, Request
56
from fastapi.openapi.models import APIKey, APIKeyIn
67
from fastapi.security.base import SecurityBase
78
from starlette import status
89

9-
from app.user.application.service.user import UserService
10+
from app.container import Container
11+
from app.user.domain.usecase.user import UserUseCase
1012
from core.exceptions import CustomException
1113

1214

@@ -34,12 +36,17 @@ async def has_permission(self, request: Request) -> bool:
3436
class IsAdmin(BasePermission):
3537
exception = UnauthorizedException
3638

37-
async def has_permission(self, request: Request) -> bool:
39+
@inject
40+
async def has_permission(
41+
self,
42+
request: Request,
43+
usecase: UserUseCase = Depends(Provide[Container.user_service]),
44+
) -> bool:
3845
user_id = request.user.id
3946
if not user_id:
4047
return False
4148

42-
return await UserService().is_admin(user_id=user_id)
49+
return await usecase.is_admin(user_id=user_id)
4350

4451

4552
class AllowAll(BasePermission):

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ types-redis = "^4.6.0.20240106"
3434
[tool.coverage.run]
3535
omit=["tests/*", "test_*.py", "migrations/*"]
3636

37+
[tool.isort]
38+
profile="black"
39+
3740
[build-system]
3841
requires = ["poetry-core>=1.0.0"]
3942
build-backend = "poetry.core.masonry.api"

tests/app/user/adapter/output/persistence/test_repository_adapter.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
from app.user.domain.repository.user import UserRepo
88
from tests.support.user_fixture import make_user
99

10-
repository_adapter = UserRepositoryAdapter()
10+
user_repo_mock = AsyncMock(spec=UserRepo)
11+
repository_adapter = UserRepositoryAdapter(user_repo=user_repo_mock)
1112

1213

1314
@pytest.mark.asyncio
1415
async def test_get_users(session: AsyncSession):
1516
# Given
1617
limit = 1
1718
prev = 1
18-
user_repo_mock = AsyncMock(spec=UserRepo)
1919
user = make_user(
2020
id=1,
2121
password="password",
@@ -45,7 +45,6 @@ async def test_get_users(session: AsyncSession):
4545
@pytest.mark.asyncio
4646
async def test_get_user_by_email_or_nickname(session: AsyncSession):
4747
# Given
48-
user_repo_mock = AsyncMock(spec=UserRepo)
4948
user = make_user(
5049
id=1,
5150
password="password",
@@ -82,7 +81,6 @@ async def test_get_user_by_email_or_nickname(session: AsyncSession):
8281
@pytest.mark.asyncio
8382
async def test_get_user_by_id(session: AsyncSession):
8483
# Given
85-
user_repo_mock = AsyncMock(spec=UserRepo)
8684
user = make_user(
8785
id=1,
8886
password="password",
@@ -115,7 +113,6 @@ async def test_get_user_by_id(session: AsyncSession):
115113
@pytest.mark.asyncio
116114
async def test_get_user_by_email_and_password(session: AsyncSession):
117115
# Given
118-
user_repo_mock = AsyncMock(spec=UserRepo)
119116
user = make_user(
120117
id=1,
121118
password="password",
@@ -151,7 +148,6 @@ async def test_get_user_by_email_and_password(session: AsyncSession):
151148
@pytest.mark.asyncio
152149
async def test_save(session: AsyncSession):
153150
# Given
154-
user_repo_mock = AsyncMock(spec=UserRepo)
155151
user = make_user(
156152
id=1,
157153
password="password",

0 commit comments

Comments
 (0)