From 67aa468329364dbd7228815c503df47c76f20573 Mon Sep 17 00:00:00 2001 From: hideyukiMORI Date: Wed, 20 May 2026 00:46:25 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20Get=20UseCase=20=E3=81=8C=20typed=20Inpu?= =?UTF-8?q?t=20DTO=20=E3=82=92=E4=BD=BF=E3=81=86=E3=82=88=E3=81=86?= =?UTF-8?q?=E7=B5=B1=E4=B8=80=20(#109)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GetNoteUseCase / GetTagUseCase / GetCommentUseCase の execute() が 生の int を受け取っていた問題を修正。他の UseCase と同様に typed DTO を使うよう統一。 追加: GetNoteInput / GetTagInput / GetCommentInput (frozen=True, slots=True) 変更: handler.py × 3、mcp.py、use_case テスト × 2 の呼び出し箇所を更新 Co-Authored-By: Claude Sonnet 4.6 --- src/example/comment/handler.py | 3 ++- src/example/comment/use_case.py | 11 ++++++++--- src/example/mcp.py | 9 ++++++--- src/example/note/handler.py | 3 ++- src/example/note/use_case.py | 11 ++++++++--- src/example/tag/handler.py | 3 ++- src/example/tag/use_case.py | 11 ++++++++--- tests/example/comment/test_comment_use_case.py | 7 ++++--- tests/example/test_mcp.py | 3 ++- 9 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/example/comment/handler.py b/src/example/comment/handler.py index ccdfbcd..aa62f3a 100644 --- a/src/example/comment/handler.py +++ b/src/example/comment/handler.py @@ -16,6 +16,7 @@ CreateCommentUseCase, DeleteCommentInput, DeleteCommentUseCase, + GetCommentInput, GetCommentUseCase, ListCommentsInput, ListCommentsUseCase, @@ -62,7 +63,7 @@ async def list_comments(note_id: int, request: Request) -> JSONResponse: @router.get("/{comment_id}") async def get_comment(note_id: int, comment_id: int) -> JSONResponse: - comment = get_use_case.execute(comment_id) + comment = get_use_case.execute(GetCommentInput(comment_id)) return JSONResponse(_comment_dict(comment)) @router.post("", status_code=201) diff --git a/src/example/comment/use_case.py b/src/example/comment/use_case.py index 362dfb3..d3363d4 100644 --- a/src/example/comment/use_case.py +++ b/src/example/comment/use_case.py @@ -39,14 +39,19 @@ def execute(self, input_: ListCommentsInput) -> ListCommentsOutput: ) +@dataclass(frozen=True, slots=True) +class GetCommentInput: + comment_id: int + + class GetCommentUseCase: def __init__(self, repository: CommentRepositoryInterface) -> None: self._repository = repository - def execute(self, comment_id: int) -> Comment: - comment = self._repository.find_by_id(comment_id) + def execute(self, input_: GetCommentInput) -> Comment: + comment = self._repository.find_by_id(input_.comment_id) if comment is None: - raise CommentNotFoundException(comment_id) + raise CommentNotFoundException(input_.comment_id) return comment diff --git a/src/example/mcp.py b/src/example/mcp.py index 322a9a3..2a00738 100644 --- a/src/example/mcp.py +++ b/src/example/mcp.py @@ -15,6 +15,7 @@ CreateCommentUseCase, DeleteCommentInput, DeleteCommentUseCase, + GetCommentInput, GetCommentUseCase, ListCommentsInput, ListCommentsUseCase, @@ -26,6 +27,7 @@ CreateNoteUseCase, DeleteNoteInput, DeleteNoteUseCase, + GetNoteInput, GetNoteUseCase, ListNotesInput, ListNotesUseCase, @@ -37,6 +39,7 @@ CreateTagUseCase, DeleteTagInput, DeleteTagUseCase, + GetTagInput, GetTagUseCase, ListTagsInput, ListTagsUseCase, @@ -80,7 +83,7 @@ def list_notes(limit: int = 20, offset: int = 0) -> list[dict]: # type: ignore[ @server.tool("Get a single note by ID.") def get_note(note_id: int) -> dict: # type: ignore[type-arg] - return asdict(note_get.execute(note_id)) + return asdict(note_get.execute(GetNoteInput(note_id=note_id))) @server.tool("Create a new note.") def create_note(title: str, body: str) -> dict: # type: ignore[type-arg] @@ -102,7 +105,7 @@ def list_tags(limit: int = 20, offset: int = 0) -> list[dict]: # type: ignore[t @server.tool("Get a single tag by ID.") def get_tag(tag_id: int) -> dict: # type: ignore[type-arg] - return asdict(tag_get.execute(tag_id)) + return asdict(tag_get.execute(GetTagInput(tag_id=tag_id))) @server.tool("Create a new tag.") def create_tag(name: str) -> dict: # type: ignore[type-arg] @@ -126,7 +129,7 @@ def list_comments(note_id: int, limit: int = 20, offset: int = 0) -> list[dict]: @server.tool("Get a single comment by ID.") def get_comment(comment_id: int) -> dict: # type: ignore[type-arg] - return asdict(comment_get.execute(comment_id)) + return asdict(comment_get.execute(GetCommentInput(comment_id=comment_id))) @server.tool("Create a new comment on a note.") def create_comment(note_id: int, body: str) -> dict: # type: ignore[type-arg] diff --git a/src/example/note/handler.py b/src/example/note/handler.py index df6c84e..49e1ff4 100644 --- a/src/example/note/handler.py +++ b/src/example/note/handler.py @@ -12,6 +12,7 @@ CreateNoteUseCase, DeleteNoteInput, DeleteNoteUseCase, + GetNoteInput, GetNoteUseCase, ListNotesInput, ListNotesUseCase, @@ -54,7 +55,7 @@ async def list_notes(request: Request) -> JSONResponse: @router.get("/{note_id}") async def get_note(note_id: int) -> JSONResponse: - note = get_use_case.execute(note_id) + note = get_use_case.execute(GetNoteInput(note_id)) return JSONResponse({"id": note.id, "title": note.title, "body": note.body}) @router.post("", status_code=201) diff --git a/src/example/note/use_case.py b/src/example/note/use_case.py index 7a767c4..04f0f68 100644 --- a/src/example/note/use_case.py +++ b/src/example/note/use_case.py @@ -50,14 +50,19 @@ def execute(self, input_: CreateNoteInput) -> Note: return self._repository.save(input_.title, input_.body) +@dataclass(frozen=True, slots=True) +class GetNoteInput: + note_id: int + + class GetNoteUseCase: def __init__(self, repository: NoteRepositoryInterface) -> None: self._repository = repository - def execute(self, note_id: int) -> Note: - note = self._repository.find_by_id(note_id) + def execute(self, input_: GetNoteInput) -> Note: + note = self._repository.find_by_id(input_.note_id) if note is None: - raise NoteNotFoundException(note_id) + raise NoteNotFoundException(input_.note_id) return note diff --git a/src/example/tag/handler.py b/src/example/tag/handler.py index 45d9939..0f0ba65 100644 --- a/src/example/tag/handler.py +++ b/src/example/tag/handler.py @@ -12,6 +12,7 @@ CreateTagUseCase, DeleteTagInput, DeleteTagUseCase, + GetTagInput, GetTagUseCase, ListTagsInput, ListTagsUseCase, @@ -52,7 +53,7 @@ async def list_tags(request: Request) -> JSONResponse: @router.get("/{tag_id}") async def get_tag(tag_id: int) -> JSONResponse: - tag = get_use_case.execute(tag_id) + tag = get_use_case.execute(GetTagInput(tag_id)) return JSONResponse({"id": tag.id, "name": tag.name}) @router.post("", status_code=201) diff --git a/src/example/tag/use_case.py b/src/example/tag/use_case.py index cb0e4a5..8c21f7f 100644 --- a/src/example/tag/use_case.py +++ b/src/example/tag/use_case.py @@ -36,14 +36,19 @@ def execute(self, input_: ListTagsInput) -> ListTagsOutput: ) +@dataclass(frozen=True, slots=True) +class GetTagInput: + tag_id: int + + class GetTagUseCase: def __init__(self, repository: TagRepositoryInterface) -> None: self._repository = repository - def execute(self, tag_id: int) -> Tag: - tag = self._repository.find_by_id(tag_id) + def execute(self, input_: GetTagInput) -> Tag: + tag = self._repository.find_by_id(input_.tag_id) if tag is None: - raise TagNotFoundException(tag_id) + raise TagNotFoundException(input_.tag_id) return tag diff --git a/tests/example/comment/test_comment_use_case.py b/tests/example/comment/test_comment_use_case.py index d8959c6..458f342 100644 --- a/tests/example/comment/test_comment_use_case.py +++ b/tests/example/comment/test_comment_use_case.py @@ -9,6 +9,7 @@ CreateCommentUseCase, DeleteCommentInput, DeleteCommentUseCase, + GetCommentInput, GetCommentUseCase, ListCommentsInput, ListCommentsUseCase, @@ -41,14 +42,14 @@ def test_list_filters_by_note_id() -> None: def test_get_returns_comment() -> None: repo = _repo() comment = CreateCommentUseCase(repo).execute(CreateCommentInput(note_id=1, body="body")) - fetched = GetCommentUseCase(repo).execute(comment.id) + fetched = GetCommentUseCase(repo).execute(GetCommentInput(comment_id=comment.id)) assert fetched == comment def test_get_raises_when_not_found() -> None: repo = _repo() with pytest.raises(CommentNotFoundException): - GetCommentUseCase(repo).execute(9999) + GetCommentUseCase(repo).execute(GetCommentInput(comment_id=9999)) def test_update_changes_body() -> None: @@ -71,7 +72,7 @@ def test_delete_removes_comment() -> None: comment = CreateCommentUseCase(repo).execute(CreateCommentInput(note_id=1, body="bye")) DeleteCommentUseCase(repo).execute(DeleteCommentInput(comment_id=comment.id)) with pytest.raises(CommentNotFoundException): - GetCommentUseCase(repo).execute(comment.id) + GetCommentUseCase(repo).execute(GetCommentInput(comment_id=comment.id)) def test_delete_raises_when_not_found() -> None: diff --git a/tests/example/test_mcp.py b/tests/example/test_mcp.py index c8c26d4..7d99b26 100644 --- a/tests/example/test_mcp.py +++ b/tests/example/test_mcp.py @@ -11,6 +11,7 @@ CreateNoteUseCase, DeleteNoteInput, DeleteNoteUseCase, + GetNoteInput, GetNoteUseCase, ListNotesInput, ListNotesUseCase, @@ -44,7 +45,7 @@ def test_note_lifecycle_via_use_cases() -> None: note = create_uc.execute(CreateNoteInput(title="MCP note", body="content")) assert list_uc.execute(ListNotesInput(limit=10, offset=0)).total == 1 - fetched = get_uc.execute(note.id) + fetched = get_uc.execute(GetNoteInput(note_id=note.id)) assert fetched.title == "MCP note" delete_uc.execute(DeleteNoteInput(note_id=note.id)) assert list_uc.execute(ListNotesInput(limit=10, offset=0)).total == 0