Skip to content

Commit af102db

Browse files
authored
utils.can_put_annotation : 引数の追加 (#728)
* add test code * put * udate * add instruction
1 parent 40abc47 commit af102db

File tree

3 files changed

+100
-6
lines changed

3 files changed

+100
-6
lines changed

.github/copilot-instructions.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# copilot-instructions.md
2+
3+
## プロジェクトの目的
4+
AnnofabのWebAPIのPythonクライアントライブラリです。
5+
6+
## 開発でよく使うコマンド
7+
* コードのフォーマット: `make format`
8+
* Lintの実行: `make lint`
9+
* テストの実行: `make test`
10+
* ドキュメントの実行: `make docs`
11+
12+
## 技術スタック
13+
* Python 3.9 以上
14+
* テストフレームワーク: Pytest v8 以上
15+
16+
## ディレクトリ構造概要
17+
18+
* `annofabcli/**`: アプリケーションのソースコード
19+
* `tests/**`: テストコード
20+
* `tests/data/**`: テストコードが参照するリソース
21+
* `docs/*`: ドキュメント
22+
23+
## コーディングスタイル
24+
25+
### Python
26+
* dictから値を取得する際、必須なキーならばブラケット記法を使う。キーが必須がどうか分からない場合は、必須とみなす。
27+
* できるだけ`os.path`でなく`pathlib.Path`を使う(Lint`flake8-use-pathlib`に従う)
28+
* Noneの判定、空文字列の判定、長さが0のコレクションの判定は、falsyとして判定するのでなく、`if a is not None:`のように判定内容を明記してください。
29+
* 型ヒントを`dict`にする場合、`dict[str, Any]`のようにキーと値の型を指定する。
30+
31+
### テストコード
32+
* Errorの確認は、`pytest.raises`を使用する。エラーメッセージの確認は行わない。
33+
* 一時ディレクトリを使用する場合は、`tmp_path` fixtureを利用する。
34+
35+
36+
## 作業の進め方
37+
* コードの修正が完了したら`make format`を実行してフォーマットを行い、その後`make lint`を実行してLintエラーがないことを確認する。
38+
39+
## レビュー
40+
* PRレビューの際は、日本語でレビューを行う

annofabapi/utils.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import dateutil
66
import dateutil.tz
77

8-
from annofabapi.models import Task, TaskHistory, TaskHistoryShort, TaskPhase
8+
from annofabapi.models import ProjectMemberRole, Task, TaskHistory, TaskHistoryShort, TaskPhase
99

1010
logger = logging.getLogger(__name__)
1111

@@ -145,17 +145,23 @@ def get_number_of_rejections(task_histories: list[TaskHistoryShort], phase: Task
145145
return rejections_by_phase
146146

147147

148-
def can_put_annotation(task: Task, my_account_id: str) -> bool:
148+
def can_put_annotation(task: Task, my_account_id: str, *, project_member_role: Optional[ProjectMemberRole] = None) -> bool:
149149
"""
150150
対象タスクが、`put_annotation` APIで、アノテーションを更新できる状態かどうか。
151151
過去に担当者が割り当たっている場合、または現在の担当者が自分自身の場合は、アノテーションを更新できる。
152152
153153
Args:
154154
task: 対象タスク
155155
my_account_id: 自分(ログインしているユーザ)のアカウントID
156+
project_member_role: プロジェクトメンバーロール。Noneの場合、プロジェクトオーナであるとみなします。
156157
157158
Returns:
158-
Trueならば、タスクの状態を変更せずに`put_annotation` APIを実行できる。
159+
Trueならば、タスクの担当者を変更せずに`put_annotation` APIを実行できる。
160+
Falseならば、タスクの担当者を変更してから、`put_annotation` APIを実行する必要がある。
159161
"""
160-
# ログインユーザはプロジェクトオーナであること前提
161-
return len(task["histories_by_phase"]) == 0 or task["account_id"] == my_account_id
162+
if project_member_role is None or project_member_role == ProjectMemberRole.OWNER:
163+
return len(task["histories_by_phase"]) == 0 or task["account_id"] == my_account_id
164+
elif project_member_role in [ProjectMemberRole.ACCEPTER, ProjectMemberRole.WORKER]:
165+
return task["account_id"] == my_account_id
166+
else:
167+
raise ValueError(f"引数'project_member_role'の値は不正です。 :: {project_member_role=}")

tests/test_utils.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
from typing import Any
44

5-
from annofabapi.models import TaskPhase
5+
from annofabapi.models import ProjectMemberRole, TaskPhase
66
from annofabapi.utils import (
7+
can_put_annotation,
78
get_number_of_rejections,
89
get_task_history_index_skipped_acceptance,
910
get_task_history_index_skipped_inspection,
@@ -614,3 +615,50 @@ def test_get_task_history_index_skipped_inspection_検査1回_教師付で提出
614615
actual = get_task_history_index_skipped_inspection(task_history_list)
615616
expected: list[int] = []
616617
assert all([a == b for a, b in zip(actual, expected)]) # noqa: C419
618+
619+
620+
class TestCanPutAnnotation:
621+
MY_ACCOUNT_ID = "12345678-abcd-1234-abcd-1234abcd5678"
622+
OTHER_ACCOUNT_ID = "87654321-dcba-4321-dcba-4321dcba8765"
623+
624+
def test_can_put_annotation_project_member_role_none_no_history(self):
625+
"""project_member_role=None, 履歴なしの場合"""
626+
task: dict[str, Any] = {"histories_by_phase": [], "account_id": None}
627+
actual = can_put_annotation(task, self.MY_ACCOUNT_ID, project_member_role=None)
628+
assert actual is True
629+
630+
def test_can_put_annotation_project_member_role_owner_with_history_different_account(self):
631+
"""project_member_role=OWNER, 履歴あり、異なるアカウントID"""
632+
task = {
633+
"histories_by_phase": [{"phase": "annotation", "phase_stage": 1, "worked": True, "account_id": self.OTHER_ACCOUNT_ID}],
634+
"account_id": self.OTHER_ACCOUNT_ID,
635+
}
636+
actual = can_put_annotation(task, self.MY_ACCOUNT_ID, project_member_role=ProjectMemberRole.OWNER)
637+
assert actual is False
638+
639+
def test_can_put_annotation_project_member_role_owner_with_history_same_account(self):
640+
"""project_member_role=OWNER, 同じアカウントID"""
641+
task = {
642+
"histories_by_phase": [{"phase": "annotation", "phase_stage": 1, "worked": True, "account_id": self.MY_ACCOUNT_ID}],
643+
"account_id": self.MY_ACCOUNT_ID,
644+
}
645+
actual = can_put_annotation(task, self.MY_ACCOUNT_ID, project_member_role=ProjectMemberRole.OWNER)
646+
assert actual is True
647+
648+
def test_can_put_annotation_project_member_role_accepter_same_account(self):
649+
"""project_member_role=ACCEPTER, 同じアカウントID"""
650+
task = {
651+
"histories_by_phase": [],
652+
"account_id": self.MY_ACCOUNT_ID,
653+
}
654+
actual = can_put_annotation(task, self.MY_ACCOUNT_ID, project_member_role=ProjectMemberRole.ACCEPTER)
655+
assert actual is True
656+
657+
def test_can_put_annotation_project_member_role_worker_same_account(self):
658+
"""project_member_role=WORKER, 同じアカウントID"""
659+
task = {
660+
"histories_by_phase": [],
661+
"account_id": self.MY_ACCOUNT_ID,
662+
}
663+
actual = can_put_annotation(task, self.MY_ACCOUNT_ID, project_member_role=ProjectMemberRole.WORKER)
664+
assert actual is True

0 commit comments

Comments
 (0)