From c847e9d8344a7cd7701c5aadb038b8b1d5e207d5 Mon Sep 17 00:00:00 2001 From: hideyukiMORI Date: Wed, 20 May 2026 01:53:06 +0900 Subject: [PATCH] fix: low-priority fixes from review (#139 equivalent) - CI matrix: add Python 3.14 - InMemoryCommentRepository.find_all_by_note: sort by id for deterministic ordering - AppSettings: add app_env validator (restrict to local/test/production) - export_openapi.py: fix docstring command path - mcp.py: add reason comment to all type: ignore[type-arg] suppression Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 2 +- src/example/comment/repository.py | 4 +++- src/example/mcp.py | 30 +++++++++++++++--------------- src/nene2/config/settings.py | 8 ++++++++ src/scripts/export_openapi.py | 2 +- 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66cffb8..6e1482c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.12"] + python-version: ["3.12", "3.14"] steps: - uses: actions/checkout@v4 diff --git a/src/example/comment/repository.py b/src/example/comment/repository.py index 7611b52..c1985d8 100644 --- a/src/example/comment/repository.py +++ b/src/example/comment/repository.py @@ -31,7 +31,9 @@ def __init__(self) -> None: self._next_id = 1 def find_all_by_note(self, note_id: int, limit: int, offset: int) -> list[Comment]: - items = [c for c in self._store.values() if c.note_id == note_id] + items = sorted( + (c for c in self._store.values() if c.note_id == note_id), key=lambda c: c.id + ) return items[offset : offset + limit] def find_by_id(self, comment_id: int) -> Comment | None: diff --git a/src/example/mcp.py b/src/example/mcp.py index f0f8bf1..6df918c 100644 --- a/src/example/mcp.py +++ b/src/example/mcp.py @@ -77,70 +77,70 @@ def create_mcp_server(settings: AppSettings | None = None) -> LocalMcpServer: ) @server.tool("List notes with optional pagination.") - def list_notes(limit: int = 20, offset: int = 0) -> list[dict]: # type: ignore[type-arg] + def list_notes(limit: int = 20, offset: int = 0) -> list[dict]: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict result = note_list.execute(ListNotesInput(limit=limit, offset=offset)) return [asdict(n) for n in result.items] @server.tool("Get a single note by ID.") - def get_note(note_id: int) -> dict: # type: ignore[type-arg] + def get_note(note_id: int) -> dict: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict 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] + def create_note(title: str, body: str) -> dict: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict return asdict(note_create.execute(CreateNoteInput(title=title, body=body))) @server.tool("Update an existing note.") - def update_note(note_id: int, title: str, body: str) -> dict: # type: ignore[type-arg] + def update_note(note_id: int, title: str, body: str) -> dict: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict return asdict(note_update.execute(UpdateNoteInput(note_id=note_id, title=title, body=body))) @server.tool("Delete a note by ID.") - def delete_note(note_id: int) -> dict: # type: ignore[type-arg] + def delete_note(note_id: int) -> dict: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict note_delete.execute(DeleteNoteInput(note_id=note_id)) return {"deleted": True, "note_id": note_id} @server.tool("List tags with optional pagination.") - def list_tags(limit: int = 20, offset: int = 0) -> list[dict]: # type: ignore[type-arg] + def list_tags(limit: int = 20, offset: int = 0) -> list[dict]: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict result = tag_list.execute(ListTagsInput(limit=limit, offset=offset)) return [asdict(t) for t in result.items] @server.tool("Get a single tag by ID.") - def get_tag(tag_id: int) -> dict: # type: ignore[type-arg] + def get_tag(tag_id: int) -> dict: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict 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] + def create_tag(name: str) -> dict: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict return asdict(tag_create.execute(CreateTagInput(name=name))) @server.tool("Update an existing tag.") - def update_tag(tag_id: int, name: str) -> dict: # type: ignore[type-arg] + def update_tag(tag_id: int, name: str) -> dict: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict return asdict(tag_update.execute(UpdateTagInput(tag_id=tag_id, name=name))) @server.tool("Delete a tag by ID.") - def delete_tag(tag_id: int) -> dict: # type: ignore[type-arg] + def delete_tag(tag_id: int) -> dict: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict tag_delete.execute(DeleteTagInput(tag_id=tag_id)) return {"deleted": True, "tag_id": tag_id} @server.tool("List comments for a note.") - def list_comments(note_id: int, limit: int = 20, offset: int = 0) -> list[dict]: # type: ignore[type-arg] + def list_comments(note_id: int, limit: int = 20, offset: int = 0) -> list[dict]: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict result = comment_list.execute( ListCommentsInput(note_id=note_id, limit=limit, offset=offset) ) return [asdict(c) for c in result.items] @server.tool("Get a single comment by ID.") - def get_comment(comment_id: int) -> dict: # type: ignore[type-arg] + def get_comment(comment_id: int) -> dict: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict 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] + def create_comment(note_id: int, body: str) -> dict: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict return asdict(comment_create.execute(CreateCommentInput(note_id=note_id, body=body))) @server.tool("Update an existing comment.") - def update_comment(comment_id: int, body: str) -> dict: # type: ignore[type-arg] + def update_comment(comment_id: int, body: str) -> dict: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict return asdict(comment_update.execute(UpdateCommentInput(comment_id=comment_id, body=body))) @server.tool("Delete a comment by ID.") - def delete_comment(comment_id: int) -> dict: # type: ignore[type-arg] + def delete_comment(comment_id: int) -> dict: # type: ignore[type-arg] # reason: mcp tool handler type stubs do not support generic dict comment_delete.execute(DeleteCommentInput(comment_id=comment_id)) return {"deleted": True, "comment_id": comment_id} diff --git a/src/nene2/config/settings.py b/src/nene2/config/settings.py index b7230b3..c414821 100644 --- a/src/nene2/config/settings.py +++ b/src/nene2/config/settings.py @@ -42,6 +42,14 @@ class AppSettings(BaseSettings): db_user: str = "" db_password: SecretStr = SecretStr("") + @field_validator("app_env") + @classmethod + def validate_app_env(cls, v: str) -> str: + allowed = {"local", "test", "production"} + if v not in allowed: + raise ValueError(f"app_env must be one of {allowed}") + return v + @field_validator("db_adapter") @classmethod def validate_adapter(cls, v: str) -> str: diff --git a/src/scripts/export_openapi.py b/src/scripts/export_openapi.py index 4135052..86ad763 100644 --- a/src/scripts/export_openapi.py +++ b/src/scripts/export_openapi.py @@ -2,7 +2,7 @@ Usage: uv run export-openapi - python scripts/export_openapi.py + uv run python src/scripts/export_openapi.py """ import json