Skip to content

20260804 1 - #1716

Open
yuji38kwmt wants to merge 5 commits into
mainfrom
20260804-1
Open

20260804 1#1716
yuji38kwmt wants to merge 5 commits into
mainfrom
20260804-1

Conversation

@yuji38kwmt

Copy link
Copy Markdown
Collaborator

No description provided.

- 新しいサブコマンド `list_all_with_replies` を実装
- コメント一覧に返信コメントを含める処理を追加
- ドキュメントを更新し、コマンドの使用例を追加
- テストケースを追加し、機能の動作を確認
Copilot AI lite review requested due to automatic review settings August 4, 2026 10:35
@kci-pr-agent

kci-pr-agent Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Title

20260804 1


Description

  • 新サブコマンドlist_all_with_replieslist_with_repliesを追加

  • create_comment_list_with_repliesユーティリティを実装

  • テストケースを追加

  • ドキュメントを更新


Changes walkthrough 📝

Relevant files
Enhancement
list_all_comment_with_replies.py
`list_all_with_replies`サブコマンドを追加                                                 
+105/-0 
list_comment_with_replies.py
`list_with_replies`サブコマンドを追加                                                         
+76/-0   
utils.py
返信コメント結合用ユーティリティを実装                                                                           
+188/-0 
Configuration changes
subcommand_comment.py
新サブコマンドをコマンド一覧に登録                                                                               
+4/-0     
Tests
test_list_all_comment_with_replies.py
返信コメントユーティリティの単体テストを追加                                                                     
+77/-0   
Documentation
index.rst
コマンド一覧に新サブコマンドを追加                                                                               
+2/-0     
list_all_with_replies.rst
`list_all_with_replies`コマンドのドキュメントを追加                                       
+96/-0   
list_with_replies.rst
`list_with_replies`コマンドのドキュメントを追加                                               
+79/-0   

Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • @kci-pr-agent

    kci-pr-agent Bot commented Aug 4, 2026

    Copy link
    Copy Markdown
    Contributor

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
    🧪 PR contains tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    重複したインポートとロガー定義

    annofabcli/comment/utils.py に同一の import logging や logger = logging.getLogger(name) が複数回定義されています。不要な重複を削除し、一度だけインポート・定義されるように整理してください。

    import logging
    from collections import defaultdict
    from collections.abc import Collection
    from typing import Any
    
    import logging
    from collections import defaultdict
    from collections.abc import Collection
    from typing import Any
    
    import logging
    from collections import defaultdict
    from collections.abc import Collection
    from typing import Any
    
    from annofabapi.models import CommentType
    
    logger = logging.getLogger(__name__)
    重複関数定義

    create_comment_list_with_replies や関連ヘルパー関数が同一ファイル内で複数回定義されています。最新の実装に統一し、不要な重複コードを削除して保守性を高めてください。

    def _get_comment_datetime_for_sorting(comment: dict[str, Any]) -> str:
        return comment["datetime_for_sorting"]
    
    
    def create_comment_list_with_replies(comment_list: Collection[dict[str, Any]]) -> list[dict[str, Any]]:
        """ルートコメントに返信コメント一覧を付与したコメント一覧を生成します。
    
        Args:
            comment_list: コメント一覧
    
        Returns:
            ``reply_comments`` を付与したルートコメント一覧
        """
    
        root_comments: list[dict[str, Any]] = []
        reply_comments_by_root_key: dict[tuple[str, str, str], list[dict[str, Any]]] = defaultdict(list)
        root_comment_key_set: set[tuple[str, str, str]] = set()
    
        for comment in comment_list:
            comment_node = comment["comment_node"]
            node_type = comment_node["_type"]
    
            if node_type == "Root":
                root_key = (comment["task_id"], comment["input_data_id"], comment["comment_id"])
                root_comments.append(comment)
                root_comment_key_set.add(root_key)
            elif node_type == "Reply":
                root_key = (comment["task_id"], comment["input_data_id"], comment_node["root_comment_id"])
                reply_comments_by_root_key[root_key].append(comment)
            else:
                logger.warning(f"未知のコメントノード種別のため、スキップします。comment_node._type='{node_type}', comment_id='{comment['comment_id']}'")
    
        for root_key in reply_comments_by_root_key:
            if root_key not in root_comment_key_set:
                logger.warning(f"返信先のルートコメントが存在しないため、返信コメントをスキップします。task_id='{root_key[0]}', input_data_id='{root_key[1]}', root_comment_id='{root_key[2]}'")
    
        sorted_root_comments = sorted(root_comments, key=_get_comment_datetime_for_sorting)
        comment_list_with_replies: list[dict[str, Any]] = []
        for root_comment in sorted_root_comments:
            root_key = (root_comment["task_id"], root_comment["input_data_id"], root_comment["comment_id"])
            reply_comments = sorted(reply_comments_by_root_key.get(root_key, []), key=_get_comment_datetime_for_sorting)
            root_comment_with_replies = dict(root_comment)
            root_comment_with_replies["reply_comments"] = [_copy_reply_comment(e) for e in reply_comments]
            comment_list_with_replies.append(root_comment_with_replies)
    
        return comment_list_with_replies
    
    
    def _copy_reply_comment(comment: dict[str, Any]) -> dict[str, Any]:
        reply_comment = dict(comment)
        reply_comment.pop("reply_count", None)
        return reply_comment
    
    
    def _get_comment_datetime_for_sorting(comment: dict[str, Any]) -> str:
        return comment["datetime_for_sorting"]
    
    
    def _create_reply_comment_for_output(comment: dict[str, Any]) -> dict[str, Any]:
        reply_comment = dict(comment)
        reply_comment.pop("reply_count", None)
        return reply_comment
    
    
    def create_comment_list_with_replies(comment_list: Collection[dict[str, Any]]) -> list[dict[str, Any]]:
        """ルートコメントに返信コメント一覧を付与したコメント一覧を生成します。
    
        Args:
            comment_list: コメント一覧
    
        Returns:
            ``reply_comments`` を付与したルートコメント一覧
        """
    
        root_comments: list[dict[str, Any]] = []
        reply_comments_by_root_key: dict[tuple[str, str, str], list[dict[str, Any]]] = defaultdict(list)
        root_comment_key_set: set[tuple[str, str, str]] = set()
    
        for comment in comment_list:
            comment_node = comment["comment_node"]
            node_type = comment_node["_type"]
    
            if node_type == "Root":
                root_key = (comment["task_id"], comment["input_data_id"], comment["comment_id"])
                root_comments.append(comment)
                root_comment_key_set.add(root_key)
            elif node_type == "Reply":
                root_key = (comment["task_id"], comment["input_data_id"], comment_node["root_comment_id"])
                reply_comments_by_root_key[root_key].append(comment)
            else:
                logger.warning(f"未知のコメントノード種別のため、スキップします。comment_node._type='{node_type}', comment_id='{comment['comment_id']}'")
    
        for root_key in reply_comments_by_root_key:
            if root_key not in root_comment_key_set:
                logger.warning(f"返信先のルートコメントが存在しないため、返信コメントをスキップします。task_id='{root_key[0]}', input_data_id='{root_key[1]}', root_comment_id='{root_key[2]}'")
    
        sorted_root_comments = sorted(root_comments, key=_get_comment_datetime_for_sorting)
        comment_list_with_replies: list[dict[str, Any]] = []
        for root_comment in sorted_root_comments:
            root_key = (root_comment["task_id"], root_comment["input_data_id"], root_comment["comment_id"])
            reply_comments = sorted(reply_comments_by_root_key.get(root_key, []), key=_get_comment_datetime_for_sorting)
            root_comment_with_replies = dict(root_comment)
            root_comment_with_replies["reply_comments"] = [_create_reply_comment_for_output(e) for e in reply_comments]
            comment_list_with_replies.append(root_comment_with_replies)
    
        return comment_list_with_replies

    Comment thread annofabcli/comment/utils.py Outdated
    Comment on lines +1 to +9
    import logging
    from collections import defaultdict
    from collections.abc import Collection
    from typing import Any

    import logging
    from collections import defaultdict
    from collections.abc import Collection
    from typing import Any

    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Suggestion: インポート文が重複して宣言されているため、1回にまとめて可読性と保守性を向上させましょう。 [general, importance: 5]

    Suggested change
    import logging
    from collections import defaultdict
    from collections.abc import Collection
    from typing import Any
    import logging
    from collections import defaultdict
    from collections.abc import Collection
    from typing import Any
    import logging
    from collections import defaultdict
    from collections.abc import Collection
    from typing import Any

    Comment on lines +141 to +143
    def _copy_reply_comment(comment: dict[str, Any]) -> dict[str, Any]:
    reply_comment = dict(comment)
    reply_comment.pop("reply_count", None)

    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Suggestion: _copy_reply_comment は機能が _create_reply_comment_for_output と重複しているため、どちらかに統一して冗長な定義を削除しましょう。 [general, importance: 5]

    Suggested change
    def _copy_reply_comment(comment: dict[str, Any]) -> dict[str, Any]:
    reply_comment = dict(comment)
    reply_comment.pop("reply_count", None)
    # 不要な `_copy_reply_comment` 関数を削除し、`_create_reply_comment_for_output` を使用します。

    Comment thread annofabcli/comment/utils.py Outdated
    return output_reply_comment


    def create_comment_list_with_replies(comment_list: Collection[dict[str, Any]]) -> list[dict[str, Any]]:

    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Suggestion: 同じ名前の関数定義が3回繰り返されており、後勝ちでしか使われないので1つに統合し、不要な重複を排除しましょう。 [possible issue, importance: 8]

    Copilot AI left a comment

    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Pull request overview

    コメント一覧出力に「ルートコメントへ返信コメント一覧(reply_comments)を付与する」機能を追加し、タスク単位・プロジェクト全体単位で取得できる新サブコマンドと、その利用方法ドキュメント/テストを追加するPRです。

    Changes:

    • comment list_with_replies / comment list_all_with_replies サブコマンドを追加
    • 返信コメントをルートコメントへぶら下げるためのユーティリティ create_comment_list_with_replies を追加
    • 新サブコマンドのドキュメントとユーティリティのテストを追加

    Reviewed changes

    Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

    Show a summary per file
    File Description
    tests/comment/test_list_all_comment_with_replies.py create_comment_list_with_replies の並び替え・返信付与・孤児返信スキップのテストを追加
    docs/command_reference/comment/list_with_replies.rst comment list_with_replies の使い方と出力例を追加
    docs/command_reference/comment/list_all_with_replies.rst comment list_all_with_replies の使い方と出力例を追加
    docs/command_reference/comment/index.rst comment配下のコマンド一覧に2コマンドを追加
    annofabcli/comment/utils.py 返信コメントをルートコメントへ集約する処理を追加(ただし現状は重複定義が混入)
    annofabcli/comment/subcommand_comment.py commentサブコマンドへ2コマンドを登録
    annofabcli/comment/list_comment_with_replies.py タスク指定で返信付きコメント一覧を出力する新コマンドを追加
    annofabcli/comment/list_all_comment_with_replies.py 全件(/条件指定)で返信付きコメント一覧を出力する新コマンドを追加
    Suppressed comments (1)

    annofabcli/comment/utils.py:99

    • _get_comment_datetime_for_sorting / create_comment_list_with_replies / _create_reply_comment_for_output などがファイル内で複数回定義されています(再定義)。Pythonでは後勝ちで上書きされるため、前半の実装が実質的に死んだコードになり、意図しない挙動・保守性低下・lintエラー(F811)につながります。実装は1箇所に集約し、重複定義を削除してください。
    def _get_comment_datetime_for_sorting(comment: dict[str, Any]) -> str:
        return comment["datetime_for_sorting"]
    
    
    def create_comment_list_with_replies(comment_list: Collection[dict[str, Any]]) -> list[dict[str, Any]]:
        """ルートコメントに返信コメント一覧を付与したコメント一覧を生成します。
    
    

    Comment thread annofabcli/comment/utils.py Outdated
    Comment on lines +1 to +22
    import logging
    from collections import defaultdict
    from collections.abc import Collection
    from typing import Any

    import logging
    from collections import defaultdict
    from collections.abc import Collection
    from typing import Any

    import logging
    from collections import defaultdict
    from collections.abc import Collection
    from typing import Any

    from annofabapi.models import CommentType

    logger = logging.getLogger(__name__)

    logger = logging.getLogger(__name__)


    Comment on lines +98 to +101
    description = (
    "すべてのルートコメントに返信コメント一覧を付与して出力します。\n"
    "コメント一覧は、コマンドを実行した日の02:00(JST)頃の状態です。最新のコメント情報を取得したい場合は、 ``annofabcli comment list`` コマンドを実行してください。"
    )

    Copilot AI left a comment

    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Pull request overview

    Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

    Suppressed comments (2)

    annofabcli/comment/list_all_comment_with_replies.py:101

    • ヘルプ文の注記が「最新のコメント情報を取得したい場合は annofabcli comment list」となっていますが、このコマンドは返信付きの出力なので、最新情報を取得するコマンドは list_with_replies の方が適切です。ドキュメント(list_all_with_replies.rst)とも不整合になります。
        description = (
            "すべてのルートコメントに返信コメント一覧を付与して出力します。\n"
            "コメント一覧は、コマンドを実行した日の02:00(JST)頃の状態です。最新のコメント情報を取得したい場合は、 ``annofabcli comment list`` コマンドを実行してください。"
        )
    

    tests/comment/test_list_all_comment_with_replies.py:73

    • このテストはWARNINGログの捕捉をpytestのデフォルト設定に依存しています。テストスイート側でログレベル設定が変わると意図した警告がcaplogに入らず、テストが不安定になる可能性があるので、対象ロガー・レベルを明示した方が安全です。
        actual = create_comment_list_with_replies([reply_comment, root_comment])
    

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    None yet

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    2 participants