Skip to content

Commit fe39e3d

Browse files
authored
Merge pull request #2 from OZ-Coding-School/day2
Day2
2 parents 319cff8 + 02a717e commit fe39e3d

File tree

9 files changed

+330
-62
lines changed

9 files changed

+330
-62
lines changed

conftest.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22

3+
from src.models.movies import MovieModel
34
from src.models.users import UserModel
45

56
TEST_BASE_URL = "http://test"
@@ -8,3 +9,4 @@
89
@pytest.fixture(scope="function", autouse=True)
910
def user_model_clear() -> None:
1011
UserModel.clear()
12+
MovieModel.clear()

main.py

+10-60
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,19 @@
1-
from typing import Annotated
2-
3-
from fastapi import FastAPI, HTTPException, Path, Query
1+
from fastapi import FastAPI
42

3+
from src.models.movies import MovieModel
54
from src.models.users import UserModel
6-
from src.schemas.users import (
7-
UserCreateRequest,
8-
UserResponse,
9-
UserSearchParams,
10-
UserUpdateRequest,
11-
)
5+
from src.routers.movie_router import movie_router
6+
from src.routers.user_router import user_router
127

138
app = FastAPI()
149

15-
UserModel.create_dummy()
16-
17-
18-
@app.post("/users")
19-
async def create_user(data: UserCreateRequest) -> int:
20-
user = UserModel(**data.model_dump())
21-
return user.id
22-
23-
24-
@app.get("/users", response_model=list[UserResponse])
25-
async def get_all_users() -> list[UserModel]:
26-
result = UserModel.all()
27-
if not result:
28-
raise HTTPException(status_code=404)
29-
return result
30-
31-
32-
@app.get("/users/search", response_model=list[UserResponse])
33-
async def search_users(query_params: Annotated[UserSearchParams, Query()]) -> list[UserModel]:
34-
valid_query = {key: value for key, value in query_params.model_dump().items() if value is not None}
35-
filtered_users = UserModel.filter(**valid_query)
36-
if not filtered_users:
37-
raise HTTPException(status_code=404)
38-
return filtered_users
39-
40-
41-
@app.get("/users/{user_id}", response_model=UserResponse)
42-
async def get_user(user_id: int = Path(gt=0)) -> UserModel:
43-
user = UserModel.get(id=user_id)
44-
if user is None:
45-
raise HTTPException(status_code=404)
46-
return user
47-
48-
49-
@app.patch("/users/{user_id}", response_model=UserResponse)
50-
async def update_user(data: UserUpdateRequest, user_id: int = Path(gt=0)) -> UserModel:
51-
user = UserModel.get(id=user_id)
52-
if user is None:
53-
raise HTTPException(status_code=404)
54-
user.update(**data.model_dump())
55-
return user
56-
57-
58-
@app.delete("/users/{user_id}")
59-
async def delete_user(user_id: int = Path(gt=0)) -> dict[str, str]:
60-
user = UserModel.get(id=user_id)
61-
if user is None:
62-
raise HTTPException(status_code=404)
63-
user.delete()
64-
65-
return {"detail": f"User: {user_id}, Successfully Deleted."}
10+
# include router in app
11+
app.include_router(user_router)
12+
app.include_router(movie_router)
6613

14+
# create dummy for test
15+
UserModel.create_dummy()
16+
MovieModel.create_dummy()
6717

6818
if __name__ == "__main__":
6919
import uvicorn

pyproject.toml

+7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ strict = true
3636
[tool.coverage.run]
3737
omit = ["*/test_*.py"]
3838

39+
[tool.coverage.report]
40+
exclude_also = [
41+
"def __repr__",
42+
"def __str__",
43+
"if __name__ == .__main__.:",
44+
]
45+
3946
[tool.pytest.ini_options]
4047
asyncio_mode = "auto"
4148
asyncio_default_fixture_loop_scope = "session"

src/models/movies.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ def get(cls, **kwargs: Any) -> MovieModel | None:
3434
@classmethod
3535
def filter(cls, **kwargs: Any) -> list[MovieModel]:
3636
"""조건에 맞는 모든 영화 리스트 반환"""
37-
return [movie for movie in cls._data if all(getattr(movie, key) == value for key, value in kwargs.items())]
37+
return [
38+
movie
39+
for movie in cls._data
40+
if all(getattr(movie, key) == value or value in getattr(movie, key) for key, value in kwargs.items())
41+
]
3842

3943
def update(self, **kwargs: Any) -> None:
4044
"""영화 정보 업데이트"""
@@ -59,9 +63,14 @@ def create_dummy(cls) -> None:
5963
cls.create(
6064
title=f"dummy_movie {i}",
6165
playtime=random.randint(100, 300),
62-
genre=random.choices(["SF", "Romantic", "Adventure", "Action", "Comedy", "Horror"]),
66+
genre=random.sample(["SF", "Romantic", "Adventure", "Action", "Comedy", "Horror"], k=3),
6367
)
6468

69+
@classmethod
70+
def clear(cls) -> None:
71+
"""모든 영화 삭제"""
72+
cls._data = []
73+
6574
def __repr__(self) -> str:
6675
return f"MovieModel(id={self.id}, title='{self.title}', playtime={self.playtime}, genre='{self.genre}')"
6776

src/routers/__init__.py

Whitespace-only changes.

src/routers/movie_router.py

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from typing import Annotated
2+
3+
from fastapi import APIRouter, HTTPException, Path, Query
4+
5+
from src.models.movies import MovieModel
6+
from src.schemas.movies import (
7+
CreateMovieRequest,
8+
MovieResponse,
9+
MovieSearchParams,
10+
MovieUpdateRequest,
11+
)
12+
13+
movie_router = APIRouter(prefix="/movies", tags=["movies"])
14+
15+
16+
@movie_router.post("", response_model=MovieResponse, status_code=201)
17+
async def create_movie(data: CreateMovieRequest) -> MovieModel:
18+
movie = MovieModel.create(**data.model_dump())
19+
return movie
20+
21+
22+
@movie_router.get("", response_model=list[MovieResponse], status_code=200)
23+
async def get_movies(query_params: Annotated[MovieSearchParams, Query()]) -> list[MovieModel]:
24+
valid_query = {key: value for key, value in query_params.model_dump().items() if value is not None}
25+
26+
if valid_query:
27+
return MovieModel.filter(**valid_query)
28+
29+
return MovieModel.all()
30+
31+
32+
@movie_router.get("/{movie_id}", response_model=MovieResponse, status_code=200)
33+
async def get_movie(movie_id: int = Path(gt=0)) -> MovieModel:
34+
movie = MovieModel.get(id=movie_id)
35+
if movie is None:
36+
raise HTTPException(status_code=404)
37+
return movie
38+
39+
40+
@movie_router.patch("/{movie_id}", response_model=MovieResponse, status_code=200)
41+
async def update_movie(data: MovieUpdateRequest, movie_id: int = Path(gt=0)) -> MovieModel:
42+
movie = MovieModel.get(id=movie_id)
43+
if movie is None:
44+
raise HTTPException(status_code=404)
45+
movie.update(**data.model_dump())
46+
return movie
47+
48+
49+
@movie_router.delete("/{movie_id}", status_code=204)
50+
async def delete_movie(movie_id: int = Path(gt=0)) -> None:
51+
movie = MovieModel.get(id=movie_id)
52+
if movie is None:
53+
raise HTTPException(status_code=404)
54+
movie.delete()

src/routers/user_router.py

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from typing import Annotated
2+
3+
from fastapi import APIRouter, HTTPException, Path, Query
4+
5+
from src.models.users import UserModel
6+
from src.schemas.users import (
7+
UserCreateRequest,
8+
UserResponse,
9+
UserSearchParams,
10+
UserUpdateRequest,
11+
)
12+
13+
user_router = APIRouter(prefix="/users", tags=["users"])
14+
15+
16+
@user_router.post("")
17+
async def create_user(data: UserCreateRequest) -> int:
18+
user = UserModel.create(**data.model_dump())
19+
return user.id
20+
21+
22+
@user_router.get("", response_model=list[UserResponse])
23+
async def get_all_users() -> list[UserModel]:
24+
result = UserModel.all()
25+
if not result:
26+
raise HTTPException(status_code=404)
27+
return result
28+
29+
30+
@user_router.get("/search", response_model=list[UserResponse])
31+
async def search_users(query_params: Annotated[UserSearchParams, Query()]) -> list[UserModel]:
32+
valid_query = {key: value for key, value in query_params.model_dump().items() if value is not None}
33+
filtered_users = UserModel.filter(**valid_query)
34+
if not filtered_users:
35+
raise HTTPException(status_code=404)
36+
return filtered_users
37+
38+
39+
@user_router.get("/{user_id}", response_model=UserResponse)
40+
async def get_user(user_id: int = Path(gt=0)) -> UserModel:
41+
user = UserModel.get(id=user_id)
42+
if user is None:
43+
raise HTTPException(status_code=404)
44+
return user
45+
46+
47+
@user_router.patch("/{user_id}", response_model=UserResponse)
48+
async def update_user(data: UserUpdateRequest, user_id: int = Path(gt=0)) -> UserModel:
49+
user = UserModel.get(id=user_id)
50+
if user is None:
51+
raise HTTPException(status_code=404)
52+
user.update(**data.model_dump())
53+
return user
54+
55+
56+
@user_router.delete("/{user_id}")
57+
async def delete_user(user_id: int = Path(gt=0)) -> dict[str, str]:
58+
user = UserModel.get(id=user_id)
59+
if user is None:
60+
raise HTTPException(status_code=404)
61+
user.delete()
62+
63+
return {"detail": f"User: {user_id}, Successfully Deleted."}

src/schemas/movies.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from typing import Annotated
2+
3+
from pydantic import BaseModel, Field
4+
5+
6+
class CreateMovieRequest(BaseModel):
7+
title: str
8+
playtime: int
9+
genre: list[str]
10+
11+
12+
class MovieResponse(BaseModel):
13+
id: int
14+
title: str
15+
playtime: int
16+
genre: list[str]
17+
18+
19+
class MovieSearchParams(BaseModel):
20+
title: str | None = None
21+
genre: str | None = None
22+
23+
24+
class MovieUpdateRequest(BaseModel):
25+
title: str | None = None
26+
playtime: Annotated[int, Field(gt=0)] | None = None
27+
genre: list[str] | None = None

0 commit comments

Comments
 (0)