annofabcli 1.102.1__py3-none-any.whl → 1.103.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. annofabcli/annotation/annotation_query.py +9 -29
  2. annofabcli/annotation/change_annotation_attributes.py +6 -14
  3. annofabcli/annotation/change_annotation_properties.py +5 -12
  4. annofabcli/annotation/copy_annotation.py +4 -10
  5. annofabcli/annotation/delete_annotation.py +10 -26
  6. annofabcli/annotation/dump_annotation.py +1 -4
  7. annofabcli/annotation/import_annotation.py +16 -40
  8. annofabcli/annotation/list_annotation.py +1 -4
  9. annofabcli/annotation/merge_segmentation.py +5 -15
  10. annofabcli/annotation/remove_segmentation_overlap.py +8 -29
  11. annofabcli/annotation/restore_annotation.py +3 -9
  12. annofabcli/annotation_specs/add_attribute_restriction.py +2 -8
  13. annofabcli/annotation_specs/attribute_restriction.py +2 -10
  14. annofabcli/annotation_specs/export_annotation_specs.py +1 -3
  15. annofabcli/annotation_specs/get_annotation_specs_with_attribute_id_replaced.py +3 -10
  16. annofabcli/annotation_specs/get_annotation_specs_with_choice_id_replaced.py +4 -10
  17. annofabcli/annotation_specs/get_annotation_specs_with_label_id_replaced.py +1 -3
  18. annofabcli/annotation_specs/list_annotation_specs_attribute.py +7 -18
  19. annofabcli/annotation_specs/list_annotation_specs_choice.py +3 -8
  20. annofabcli/annotation_specs/list_annotation_specs_history.py +0 -1
  21. annofabcli/annotation_specs/list_annotation_specs_label.py +3 -8
  22. annofabcli/annotation_specs/list_annotation_specs_label_attribute.py +4 -9
  23. annofabcli/annotation_specs/list_attribute_restriction.py +3 -9
  24. annofabcli/annotation_specs/put_label_color.py +1 -6
  25. annofabcli/comment/delete_comment.py +3 -9
  26. annofabcli/comment/list_all_comment.py +2 -4
  27. annofabcli/comment/list_comment.py +1 -4
  28. annofabcli/comment/put_comment.py +4 -13
  29. annofabcli/comment/put_comment_simply.py +2 -6
  30. annofabcli/comment/put_inspection_comment.py +2 -6
  31. annofabcli/comment/put_inspection_comment_simply.py +3 -6
  32. annofabcli/comment/put_onhold_comment.py +2 -6
  33. annofabcli/comment/put_onhold_comment_simply.py +2 -4
  34. annofabcli/common/cli.py +5 -43
  35. annofabcli/common/download.py +8 -25
  36. annofabcli/common/image.py +5 -9
  37. annofabcli/common/utils.py +1 -3
  38. annofabcli/common/visualize.py +2 -4
  39. annofabcli/filesystem/draw_annotation.py +8 -20
  40. annofabcli/filesystem/filter_annotation.py +7 -24
  41. annofabcli/filesystem/mask_user_info.py +3 -6
  42. annofabcli/filesystem/merge_annotation.py +2 -6
  43. annofabcli/input_data/change_input_data_name.py +3 -7
  44. annofabcli/input_data/copy_input_data.py +6 -14
  45. annofabcli/input_data/delete_input_data.py +7 -24
  46. annofabcli/input_data/delete_metadata_key_of_input_data.py +5 -16
  47. annofabcli/input_data/list_all_input_data.py +5 -14
  48. annofabcli/input_data/list_all_input_data_merged_task.py +8 -23
  49. annofabcli/input_data/list_input_data.py +5 -16
  50. annofabcli/input_data/put_input_data.py +7 -19
  51. annofabcli/input_data/update_metadata_of_input_data.py +6 -14
  52. annofabcli/instruction/list_instruction_history.py +0 -1
  53. annofabcli/instruction/upload_instruction.py +1 -4
  54. annofabcli/job/list_job.py +1 -2
  55. annofabcli/job/list_last_job.py +1 -3
  56. annofabcli/organization/list_organization.py +0 -1
  57. annofabcli/organization_member/change_organization_member.py +1 -3
  58. annofabcli/organization_member/delete_organization_member.py +2 -6
  59. annofabcli/organization_member/invite_organization_member.py +1 -3
  60. annofabcli/organization_member/list_organization_member.py +0 -1
  61. annofabcli/project/change_organization_of_project.py +257 -0
  62. annofabcli/project/change_project_status.py +2 -2
  63. annofabcli/project/copy_project.py +2 -7
  64. annofabcli/project/diff_projects.py +4 -16
  65. annofabcli/project/list_project.py +0 -1
  66. annofabcli/project/put_project.py +2 -6
  67. annofabcli/project/subcommand_project.py +2 -0
  68. annofabcli/project_member/change_project_members.py +2 -2
  69. annofabcli/project_member/copy_project_members.py +2 -7
  70. annofabcli/project_member/drop_project_members.py +1 -3
  71. annofabcli/project_member/invite_project_members.py +1 -3
  72. annofabcli/project_member/list_users.py +0 -1
  73. annofabcli/project_member/put_project_members.py +4 -12
  74. annofabcli/stat_visualization/mask_visualization_dir.py +6 -16
  75. annofabcli/stat_visualization/merge_visualization_dir.py +6 -18
  76. annofabcli/stat_visualization/summarize_whole_performance_csv.py +3 -7
  77. annofabcli/stat_visualization/write_graph.py +5 -15
  78. annofabcli/stat_visualization/write_performance_rating_csv.py +4 -12
  79. annofabcli/statistics/list_annotation_area.py +3 -7
  80. annofabcli/statistics/list_annotation_attribute.py +6 -15
  81. annofabcli/statistics/list_annotation_attribute_filled_count.py +9 -23
  82. annofabcli/statistics/list_annotation_count.py +18 -44
  83. annofabcli/statistics/list_annotation_duration.py +14 -40
  84. annofabcli/statistics/list_video_duration.py +2 -3
  85. annofabcli/statistics/list_worktime.py +0 -1
  86. annofabcli/statistics/scatter.py +3 -9
  87. annofabcli/statistics/summarize_task_count.py +7 -12
  88. annofabcli/statistics/summarize_task_count_by_task_id_group.py +3 -11
  89. annofabcli/statistics/summarize_task_count_by_user.py +1 -5
  90. annofabcli/statistics/visualization/dataframe/annotation_count.py +1 -3
  91. annofabcli/statistics/visualization/dataframe/cumulative_productivity.py +3 -9
  92. annofabcli/statistics/visualization/dataframe/productivity_per_date.py +11 -23
  93. annofabcli/statistics/visualization/dataframe/project_performance.py +1 -3
  94. annofabcli/statistics/visualization/dataframe/task.py +2 -5
  95. annofabcli/statistics/visualization/dataframe/task_worktime_by_phase_user.py +6 -20
  96. annofabcli/statistics/visualization/dataframe/user_performance.py +29 -88
  97. annofabcli/statistics/visualization/dataframe/whole_performance.py +4 -10
  98. annofabcli/statistics/visualization/dataframe/whole_productivity_per_date.py +17 -49
  99. annofabcli/statistics/visualization/dataframe/worktime_per_date.py +3 -9
  100. annofabcli/statistics/visualization/filtering_query.py +2 -6
  101. annofabcli/statistics/visualization/project_dir.py +9 -26
  102. annofabcli/statistics/visualization/visualization_source_files.py +3 -10
  103. annofabcli/statistics/visualize_annotation_count.py +7 -21
  104. annofabcli/statistics/visualize_annotation_duration.py +7 -17
  105. annofabcli/statistics/visualize_statistics.py +17 -52
  106. annofabcli/statistics/visualize_video_duration.py +8 -19
  107. annofabcli/supplementary/delete_supplementary_data.py +7 -23
  108. annofabcli/supplementary/list_supplementary_data.py +1 -1
  109. annofabcli/supplementary/put_supplementary_data.py +5 -15
  110. annofabcli/task/cancel_acceptance.py +3 -4
  111. annofabcli/task/change_operator.py +3 -11
  112. annofabcli/task/change_status_to_break.py +1 -1
  113. annofabcli/task/change_status_to_on_hold.py +5 -18
  114. annofabcli/task/complete_tasks.py +8 -25
  115. annofabcli/task/copy_tasks.py +2 -3
  116. annofabcli/task/delete_metadata_key_of_task.py +2 -6
  117. annofabcli/task/delete_tasks.py +7 -25
  118. annofabcli/task/list_all_tasks.py +2 -4
  119. annofabcli/task/list_tasks.py +2 -6
  120. annofabcli/task/list_tasks_added_task_history.py +7 -21
  121. annofabcli/task/put_tasks.py +2 -3
  122. annofabcli/task/put_tasks_by_count.py +3 -7
  123. annofabcli/task/reject_tasks.py +7 -19
  124. annofabcli/task/update_metadata_of_task.py +1 -1
  125. annofabcli/task_history/list_all_task_history.py +2 -5
  126. annofabcli/task_history/list_task_history.py +0 -1
  127. annofabcli/task_history_event/list_all_task_history_event.py +4 -11
  128. annofabcli/task_history_event/list_worktime.py +4 -14
  129. {annofabcli-1.102.1.dist-info → annofabcli-1.103.0.dist-info}/METADATA +1 -1
  130. annofabcli-1.103.0.dist-info/RECORD +215 -0
  131. annofabcli-1.102.1.dist-info/RECORD +0 -214
  132. {annofabcli-1.102.1.dist-info → annofabcli-1.103.0.dist-info}/WHEEL +0 -0
  133. {annofabcli-1.102.1.dist-info → annofabcli-1.103.0.dist-info}/entry_points.txt +0 -0
  134. {annofabcli-1.102.1.dist-info → annofabcli-1.103.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,257 @@
1
+ import argparse
2
+ import asyncio
3
+ import copy
4
+ import logging
5
+ import time
6
+ from typing import Any, Optional
7
+
8
+ import annofabapi
9
+ import more_itertools
10
+ import requests
11
+ from annofabapi.models import JobStatus, OrganizationMemberRole, ProjectJobType
12
+
13
+ import annofabcli
14
+ from annofabcli.common.cli import (
15
+ CommandLine,
16
+ CommandLineWithConfirm,
17
+ build_annofabapi_resource_and_login,
18
+ )
19
+ from annofabcli.common.facade import AnnofabApiFacade
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class ChangeProjectOrganizationMain(CommandLineWithConfirm):
25
+ def __init__(
26
+ self,
27
+ service: annofabapi.Resource,
28
+ *,
29
+ is_force: bool = False,
30
+ all_yes: bool = False,
31
+ ) -> None:
32
+ self.service = service
33
+ self.is_force = is_force
34
+ self.facade = AnnofabApiFacade(service)
35
+ super().__init__(all_yes)
36
+
37
+ async def wait_until_jobs_finished_async(self, jobs: list[dict[str, Any]]) -> None:
38
+ tasks = [
39
+ self.wait_until_job_finished_async(
40
+ project_id=job["project_id"],
41
+ job_type=ProjectJobType(job["job_type"]),
42
+ job_id=job["job_id"],
43
+ job_access_interval=60,
44
+ max_job_access=360,
45
+ )
46
+ for job in jobs
47
+ ]
48
+ results = await asyncio.gather(*tasks)
49
+ success_count = 0
50
+ for result in results:
51
+ if result is not None and result == JobStatus.SUCCEEDED:
52
+ success_count += 1
53
+
54
+ logger.info(f"{success_count} 件のプロジェクトの組織の変更が成功しました。")
55
+
56
+ async def wait_until_job_finished_async(
57
+ self,
58
+ project_id: str,
59
+ job_type: ProjectJobType,
60
+ job_id: str,
61
+ job_access_interval: int = 60,
62
+ max_job_access: int = 360,
63
+ ) -> Optional["JobStatus"]:
64
+ """
65
+ 指定したジョブが終了するまで非同期で待つ。
66
+
67
+ Args:
68
+ project_id: プロジェクトID
69
+ job_type: ジョブ種別
70
+ job_id: ジョブID。Noneの場合は、現在進行中のジョブが終了するまで待つ。
71
+ job_access_interval: ジョブにアクセスする間隔[sec]
72
+ max_job_access: ジョブに最大何回アクセスするか
73
+
74
+ Returns:
75
+ 指定した時間(アクセス頻度と回数)待った後のジョブのステータスを返す。
76
+ 指定したジョブ(job_idがNoneの場合は現在進行中のジョブ)が存在しない場合は、Noneを返す。
77
+ """
78
+
79
+ def get_job_from_job_id(arg_job_id: str) -> Optional[dict[str, Any]]:
80
+ content, _ = self.service.api.get_project_job(project_id, query_params={"type": job_type.value})
81
+ job_list = content["list"]
82
+ return more_itertools.first_true(job_list, pred=lambda e: e["job_id"] == arg_job_id)
83
+
84
+ job_access_count = 0
85
+ while True:
86
+ # API呼び出しは同期なので、スレッドでラップ
87
+ job = get_job_from_job_id(job_id)
88
+ if job is None:
89
+ logger.info(
90
+ "project_id='%s', job_id='%s', job_type='%s' のジョブは存在しません。",
91
+ project_id,
92
+ job_type.value,
93
+ job_id,
94
+ )
95
+ return None
96
+
97
+ job_access_count += 1
98
+
99
+ if job["job_status"] == JobStatus.SUCCEEDED.value:
100
+ logger.info(
101
+ "project_id='%s', job_id='%s', job_type='%s' のジョブが成功しました。",
102
+ project_id,
103
+ job_id,
104
+ job_type.value,
105
+ )
106
+ return JobStatus.SUCCEEDED
107
+
108
+ elif job["job_status"] == JobStatus.FAILED.value:
109
+ logger.info(
110
+ "project_id='%s', job_id='%s', job_type='%s' のジョブが失敗しました。:: errors='%s'",
111
+ project_id,
112
+ job_id,
113
+ job_type.value,
114
+ job["errors"],
115
+ )
116
+ return JobStatus.FAILED
117
+
118
+ elif job_access_count < max_job_access:
119
+ logger.info(
120
+ "project_id='%s', job_id='%s', job_type='%s' のジョブは進行中です。%d 秒間待ちます。",
121
+ project_id,
122
+ job_id,
123
+ job_type.value,
124
+ job_access_interval,
125
+ )
126
+ await asyncio.sleep(job_access_interval)
127
+ else:
128
+ logger.info(
129
+ "project_id='%s', job_id='%s', job_type='%s' のジョブは %.1f 分以上経過しても、終了しませんでした。",
130
+ project_id,
131
+ job["job_id"],
132
+ job_type.value,
133
+ job_access_interval * job_access_count / 60,
134
+ )
135
+ return JobStatus.PROGRESS
136
+
137
+ def change_organization_for_project(self, project_id: str, organization_name: str) -> Optional[dict[str, Any]]:
138
+ project = self.service.wrapper.get_project_or_none(project_id)
139
+ if project is None:
140
+ logger.warning(f"project_id='{project_id}'のプロジェクトは存在しないので、スキップします。")
141
+ return None
142
+
143
+ project_name = project["title"]
144
+ if project["project_status"] == "active":
145
+ if self.is_force:
146
+ if not self.confirm_processing(
147
+ f"project_id='{project_id}'のプロジェクトの状態を停止中にしたあと、所属する組織を'{organization_name}'に変更しますか? :: project_name='{project_name}'"
148
+ ):
149
+ return None
150
+ request_body = copy.deepcopy(project)
151
+ request_body.update(
152
+ {
153
+ "status": "suspended",
154
+ "last_updated_datetime": project["updated_datetime"],
155
+ }
156
+ )
157
+ project, _ = self.service.api.put_project(project_id, request_body=request_body, query_params={"v": "2"})
158
+ logger.info(f"project_id='{project_id}'のプロジェクトのステータスを「停止中」に変更しました。 :: project_name='{project_name}'")
159
+ else:
160
+ logger.warning(
161
+ f"project_id='{project_id}'のプロジェクトのステータスは「進行中」のため、組織を変更できません。 `--force`オプションを指定すれば、停止中状態に変更した後組織を変更できます。"
162
+ )
163
+ return None
164
+ elif not self.confirm_processing(f"project_id='{project_id}'のプロジェクトの組織を'{organization_name}'に変更しますか? :: project_name='{project_name}'"):
165
+ return None
166
+
167
+ assert project is not None
168
+ request_body = copy.deepcopy(project)
169
+ request_body["organization_name"] = organization_name
170
+ request_body["last_updated_datetime"] = project["updated_datetime"]
171
+ request_body["status"] = project["project_status"]
172
+
173
+ content, _ = self.service.api.put_project(project_id, request_body=request_body, query_params={"v": "2"})
174
+ job = content["job"]
175
+ logger.info(f"project_id='{project_id}'のプロジェクトの所属先組織を'{organization_name}'に変更するジョブを発行しました。 :: project_name='{project_name}', job_id='{job['job_id']}'")
176
+ return job
177
+
178
+ def change_organization_for_project_list(self, project_id_list: list[str], organization_name: str) -> list[dict[str, Any]]:
179
+ if not self.facade.contains_any_organization_member_role(organization_name, [OrganizationMemberRole.OWNER, OrganizationMemberRole.ADMINISTRATOR]):
180
+ logger.warning(f"変更先組織'{organization_name}'に対して管理者ロールまたはオーナロールでないため、プロジェクトの所属する組織を変更できません。")
181
+ return []
182
+
183
+ logger.info(f"{len(project_id_list)} 件のプロジェクトの組織を'{organization_name}'に変更するジョブを発行します。")
184
+
185
+ job_list = []
186
+ for project_id in project_id_list:
187
+ try:
188
+ result = self.change_organization_for_project(project_id, organization_name)
189
+ if result is not None:
190
+ job_list.append(result)
191
+ except requests.HTTPError:
192
+ logger.warning(f"project_id='{project_id}'の組織変更でHTTPエラーが発生しました。", exc_info=True)
193
+ logger.info(f"{len(job_list)}/{len(project_id_list)}件のプロジェクトの組織を'{organization_name}'に変更するジョブを発行しました。")
194
+ return job_list
195
+
196
+
197
+ class ChangeProjectOrganization(CommandLine):
198
+ def main(self) -> None:
199
+ args = self.args
200
+ project_id_list = annofabcli.common.cli.get_list_from_args(args.project_id)
201
+ main_obj = ChangeProjectOrganizationMain(self.service, all_yes=args.yes, is_force=args.force)
202
+
203
+ job_list = main_obj.change_organization_for_project_list(project_id_list=project_id_list, organization_name=args.organization)
204
+ if len(job_list) == 0:
205
+ logger.info("組織を変更するジョブは発行されませんでした。終了します。")
206
+ return
207
+
208
+ # APIリクエストを減らすため、とりあえず60秒待ちます
209
+ seconds = 60
210
+ logger.info(f"ジョブの完了を{seconds}秒待ちます。")
211
+ time.sleep(seconds)
212
+
213
+ # すべてのジョブが完了するまで待つ
214
+ asyncio.run(main_obj.wait_until_jobs_finished_async(job_list))
215
+
216
+
217
+ def main(args: argparse.Namespace) -> None:
218
+ service = build_annofabapi_resource_and_login(args)
219
+ facade = AnnofabApiFacade(service)
220
+ ChangeProjectOrganization(service, facade, args).main()
221
+
222
+
223
+ def parse_args(parser: argparse.ArgumentParser) -> None:
224
+ parser.add_argument(
225
+ "-p",
226
+ "--project_id",
227
+ type=str,
228
+ required=True,
229
+ nargs="+",
230
+ help="対象プロジェクトのproject_idを指定します。 ``file://`` を先頭に付けると、project_idの一覧が記載されたファイルを指定できます。",
231
+ )
232
+
233
+ parser.add_argument(
234
+ "-org",
235
+ "--organization",
236
+ type=str,
237
+ required=True,
238
+ help="変更後の組織名を指定してください。",
239
+ )
240
+
241
+ parser.add_argument(
242
+ "--force",
243
+ action="store_true",
244
+ help="強制的に組織を変更します(将来拡張用)。",
245
+ )
246
+
247
+ parser.set_defaults(subcommand_func=main)
248
+
249
+
250
+ def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
251
+ subcommand_name = "change_organization"
252
+ subcommand_help = "プロジェクトの所属する組織を変更します。"
253
+ epilog = "プロジェクトのオーナロール、変更先の組織の管理者またはオーナーロールを持つユーザで実行してください。"
254
+
255
+ parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, epilog=epilog)
256
+ parse_args(parser)
257
+ return parser
@@ -112,7 +112,7 @@ class ChanegProjectStatusMain:
112
112
  return False
113
113
 
114
114
  if not self.facade.contains_any_project_member_role(project_id, [ProjectMemberRole.OWNER]):
115
- logger.warning(f"project_id={project_id}: オーナロールでないため、アノテーションzipを更新できません。project_title={project['title']}")
115
+ logger.warning(f"project_id={project_id}: オーナロールでないため、プロジェクトのステータスを変更できません。project_title={project['title']}")
116
116
  return False
117
117
 
118
118
  logger.debug(f"{project['title']} のステータスを{status.value} に変更します。project_id={project_id}")
@@ -187,7 +187,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
187
187
  parser.add_argument(
188
188
  "--force",
189
189
  action="store_true",
190
- help=f"`--status {ProjectStatus.SUSPENDED.value}`を指定している状態で、 ``--force`` を指定した場合、作業中タスクが残っていても停止状態に変更します。", # noqa: E501
190
+ help=f"`--status {ProjectStatus.SUSPENDED.value}`を指定している状態で、 ``--force`` を指定した場合、作業中タスクが残っていても停止状態に変更します。",
191
191
  )
192
192
 
193
193
  parser.set_defaults(subcommand_func=main)
@@ -84,10 +84,7 @@ class CopyProject(CommandLine):
84
84
  if copied_targets is not None:
85
85
  logger.info(f"コピー対象: {[e.value for e in copied_targets]}")
86
86
 
87
- confirm_message = (
88
- f"プロジェクト'{src_project_title}'(project_id='{src_project_id}')を、"
89
- f"プロジェクト'{dest_title}'(project_id='{dest_project_id}') にコピーしますか?"
90
- )
87
+ confirm_message = f"プロジェクト'{src_project_title}'(project_id='{src_project_id}')を、プロジェクト'{dest_title}'(project_id='{dest_project_id}') にコピーしますか?"
91
88
  if not self.confirm_processing(confirm_message):
92
89
  logger.info(f"プロジェクト'{src_project_title}'(project_id='{src_project_id}')をコピーせずに終了します。")
93
90
  return
@@ -142,9 +139,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
142
139
 
143
140
  argument_parser.add_project_id(help_message="コピー元のプロジェクトのproject_idを指定してください。")
144
141
 
145
- parser.add_argument(
146
- "--dest_project_id", type=str, help="新しいプロジェクトのproject_idを指定してください。省略した場合は UUIDv4 フォーマットになります。"
147
- )
142
+ parser.add_argument("--dest_project_id", type=str, help="新しいプロジェクトのproject_idを指定してください。省略した場合は UUIDv4 フォーマットになります。")
148
143
  parser.add_argument("--dest_title", type=str, required=True, help="新しいプロジェクトのタイトルを指定してください。")
149
144
  parser.add_argument("--dest_overview", type=str, help="新しいプロジェクトの概要を指定してください。")
150
145
 
@@ -105,11 +105,7 @@ class DiffProjects(CommandLine):
105
105
  user_ids2 = [e["user_id"] for e in sorted_members2]
106
106
 
107
107
  if user_ids1 != user_ids2:
108
- diff_message += (
109
- f"### user_idのListに差分あり\n"
110
- f"set(user_ids1) - set(user_ids2) = {set(user_ids1) - set(user_ids2)}\n"
111
- f"set(user_ids2) - set(user_ids1) = {set(user_ids2) - set(user_ids1)}\n"
112
- )
108
+ diff_message += f"### user_idのListに差分あり\nset(user_ids1) - set(user_ids2) = {set(user_ids1) - set(user_ids2)}\nset(user_ids2) - set(user_ids1) = {set(user_ids2) - set(user_ids1)}\n"
113
109
 
114
110
  return True, diff_message
115
111
 
@@ -286,12 +282,8 @@ class DiffProjects(CommandLine):
286
282
  diff_message += message
287
283
 
288
284
  if DiffTarget.ANNOTATION_LABELS in diff_targets:
289
- labels1_v1 = convert_annotation_specs_labels_v2_to_v1(
290
- labels_v2=annotation_specs1["labels"], additionals_v2=annotation_specs1["additionals"]
291
- )
292
- labels2_v1 = convert_annotation_specs_labels_v2_to_v1(
293
- labels_v2=annotation_specs2["labels"], additionals_v2=annotation_specs2["additionals"]
294
- )
285
+ labels1_v1 = convert_annotation_specs_labels_v2_to_v1(labels_v2=annotation_specs1["labels"], additionals_v2=annotation_specs1["additionals"])
286
+ labels2_v1 = convert_annotation_specs_labels_v2_to_v1(labels_v2=annotation_specs2["labels"], additionals_v2=annotation_specs2["additionals"])
295
287
 
296
288
  bool_result, message = self.diff_labels_of_annotation_specs(labels1_v1, labels2_v1)
297
289
  is_different = is_different or bool_result
@@ -393,11 +385,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
393
385
  nargs="+",
394
386
  choices=[e.value for e in choices],
395
387
  default=["annotation_labels", "inspection_phrases", "members", "settings"],
396
- help="比較する項目。指定しなければ全項目を比較する。"
397
- "annotation_labels: アノテーション仕様のラベル情報, "
398
- "inspection_phrases: 定型指摘,"
399
- "members: プロジェクトメンバ,"
400
- "settings: プロジェクト設定,",
388
+ help="比較する項目。指定しなければ全項目を比較する。annotation_labels: アノテーション仕様のラベル情報, inspection_phrases: 定型指摘,members: プロジェクトメンバ,settings: プロジェクト設定,",
401
389
  )
402
390
 
403
391
  parser.set_defaults(subcommand_func=main)
@@ -250,7 +250,6 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
250
250
  default=FormatArgument.CSV,
251
251
  )
252
252
  argument_parser.add_output()
253
- argument_parser.add_csv_format()
254
253
 
255
254
  parser.set_defaults(subcommand_func=main)
256
255
 
@@ -65,8 +65,7 @@ class PutProject(CommandLine):
65
65
  }
66
66
  new_project, _ = self.service.api.put_project(new_project_id, request_body=request_body)
67
67
  logger.info(
68
- f"'{organization}'組織に、project_id='{new_project['project_id']}'のプロジェクトを作成しました。 :: "
69
- f"title='{new_project['title']}', input_data_type='{new_project['input_data_type']}'"
68
+ f"'{organization}'組織に、project_id='{new_project['project_id']}'のプロジェクトを作成しました。 :: title='{new_project['title']}', input_data_type='{new_project['input_data_type']}'"
70
69
  )
71
70
 
72
71
  COMMON_MESSAGE = "annofabcli project put: error:"
@@ -114,10 +113,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
114
113
  type=str,
115
114
  choices=[e.value for e in InputDataType],
116
115
  required=True,
117
- help="プロジェクトに登録する入力データの種類\n\n"
118
- f" * {InputDataType.IMAGE.value} : 画像\n"
119
- f" * {InputDataType.MOVIE.value} : 動画\n"
120
- f" * {InputDataType.CUSTOM.value} : カスタム(点群など)",
116
+ help=f"プロジェクトに登録する入力データの種類\n\n * {InputDataType.IMAGE.value} : 画像\n * {InputDataType.MOVIE.value} : 動画\n * {InputDataType.CUSTOM.value} : カスタム(点群など)",
121
117
  )
122
118
 
123
119
  parser.add_argument("-p", "--project_id", type=str, required=False, help="作成するプロジェクトのproject_id。未指定の場合はUUIDv4になります。")
@@ -1,6 +1,7 @@
1
1
  import argparse
2
2
  from typing import Optional
3
3
 
4
+ import annofabcli.project.change_organization_of_project
4
5
  import annofabcli.project.change_project_status
5
6
  import annofabcli.project.copy_project
6
7
  import annofabcli.project.diff_projects
@@ -13,6 +14,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
13
14
  subparsers = parser.add_subparsers(dest="subcommand_name")
14
15
 
15
16
  # サブコマンドの定義
17
+ annofabcli.project.change_organization_of_project.add_parser(subparsers)
16
18
  annofabcli.project.change_project_status.add_parser(subparsers)
17
19
  annofabcli.project.copy_project.add_parser(subparsers)
18
20
  annofabcli.project.diff_projects.add_parser(subparsers)
@@ -147,7 +147,7 @@ class ChangeProjectMembers(CommandLine):
147
147
  def main(self) -> None:
148
148
  args = self.args
149
149
  project_id = args.project_id
150
- if args.all_user:
150
+ if args.all_user: # noqa: SIM108
151
151
  user_id_list = self.get_all_user_id_list_except_myself(project_id)
152
152
  else:
153
153
  user_id_list = annofabcli.common.cli.get_list_from_args(args.user_id)
@@ -193,7 +193,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
193
193
  parser.add_argument(
194
194
  "--member_info",
195
195
  type=str,
196
- help="プロジェクトメンバに対して設定するメンバ情報を、JSON形式で指定します。 ``file://`` を先頭に付けると、JSON形式のファイルを指定できます。 " # noqa: E501
196
+ help="プロジェクトメンバに対して設定するメンバ情報を、JSON形式で指定します。 ``file://`` を先頭に付けると、JSON形式のファイルを指定できます。 "
197
197
  "以下のキーが指定可能です。sampling_inspection_rate, sampling_acceptance_rate, "
198
198
  "未設定にする場合は、値にnullを指定してください。"
199
199
  "詳細は https://annofab.com/docs/api/#operation/putProjectMember を参照ください。 ",
@@ -120,10 +120,7 @@ class CopyProjectMembers(CommandLine):
120
120
  account_id = member["account_id"]
121
121
  if self.find_member(dest_organization_members, account_id) is None:
122
122
  # コピー先の組織メンバでないので、コピーしない
123
- logger.debug(
124
- f"コピーしないメンバ: {member['user_id']} , {member['username']} : "
125
- f"(コピー先の所属組織 {dest_organization_name} の組織メンバでないため)"
126
- )
123
+ logger.debug(f"コピーしないメンバ: {member['user_id']} , {member['username']} : (コピー先の所属組織 {dest_organization_name} の組織メンバでないため)")
127
124
  continue
128
125
 
129
126
  added_members.append(member)
@@ -152,9 +149,7 @@ class CopyProjectMembers(CommandLine):
152
149
 
153
150
  if len(added_members) > 0:
154
151
  if self.confirm_processing(
155
- f"'{self.src_project_title}' のプロジェクトのメンバを、"
156
- f"'{self.dest_project_title}' にコピーしますか?"
157
- f"追加対象: {len(added_members)} 件, 削除対象: {len(deleted_dest_members)} 件"
152
+ f"'{self.src_project_title}' のプロジェクトのメンバを、'{self.dest_project_title}' にコピーしますか?追加対象: {len(added_members)} 件, 削除対象: {len(deleted_dest_members)} 件"
158
153
  ):
159
154
  self.put_project_members(dest_project_id, updated_members)
160
155
  else:
@@ -51,9 +51,7 @@ class DropProjectMembersMain:
51
51
  self.service.api.put_project_member(project_id, user_id, request_body=request_body)
52
52
  logger.debug(f"プロジェクト'{project_title}'(project_id='{project_id}') から、user_id='{user_id}'のユーザーを脱退させました。")
53
53
  except requests.HTTPError:
54
- logger.warning(
55
- f"プロジェクト'{project_title}'(project_id='{project_id}') から、user_id='{user_id}'のユーザーを脱退させられませんでした。"
56
- )
54
+ logger.warning(f"プロジェクト'{project_title}'(project_id='{project_id}') から、user_id='{user_id}'のユーザーを脱退させられませんでした。")
57
55
 
58
56
  def drop_role_with_organization(self, organization_name: str, user_id_list: list[str]): # noqa: ANN201
59
57
  projects = self.service.wrapper.get_all_projects_of_organization(organization_name, query_params={"account_id": self.service.api.account_id})
@@ -131,9 +131,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
131
131
  nargs="+",
132
132
  help="招待するプロジェクトのproject_idを指定してください。 ``file://`` を先頭に付けると、一覧が記載されたファイルを指定できます。",
133
133
  )
134
- assign_group.add_argument(
135
- "-org", "--organization", type=str, help="組織名を指定すると、組織配下のすべてのプロジェクト(自分が所属している)に招待します。"
136
- )
134
+ assign_group.add_argument("-org", "--organization", type=str, help="組織名を指定すると、組織配下のすべてのプロジェクト(自分が所属している)に招待します。")
137
135
 
138
136
  parser.set_defaults(subcommand_func=main)
139
137
 
@@ -109,7 +109,6 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
109
109
  default=FormatArgument.CSV,
110
110
  )
111
111
  argument_parser.add_output()
112
- argument_parser.add_csv_format()
113
112
 
114
113
  parser.set_defaults(subcommand_func=main)
115
114
 
@@ -97,9 +97,7 @@ class PutProjectMembers(CommandLine):
97
97
  logger.warning(f"ユーザ '{member.user_id}' は、'{organization_name}' 組織の組織メンバでないため、登録できませんでした。")
98
98
  continue
99
99
 
100
- message_for_confirm = (
101
- f"ユーザ '{member.user_id}'を、プロジェクト'{project_title}'のメンバーに登録しますか? member_role='{member.member_role.value}'"
102
- )
100
+ message_for_confirm = f"ユーザ '{member.user_id}'を、プロジェクト'{project_title}'のメンバーに登録しますか? member_role='{member.member_role.value}'"
103
101
  if not self.confirm_processing(message_for_confirm):
104
102
  continue
105
103
 
@@ -110,9 +108,7 @@ class PutProjectMembers(CommandLine):
110
108
  count_invite_members += 1
111
109
 
112
110
  except requests.exceptions.HTTPError:
113
- logger.warning(
114
- f"プロジェクトメンバの登録に失敗しました。user_id = '{member.user_id}', member_role = '{member.member_role.value}'", exc_info=True
115
- )
111
+ logger.warning(f"プロジェクトメンバの登録に失敗しました。user_id = '{member.user_id}', member_role = '{member.member_role.value}'", exc_info=True)
116
112
 
117
113
  logger.info(f"プロジェクト'{project_title}' に、{count_invite_members} / {len(members)} 件のプロジェクトメンバを登録しました。")
118
114
 
@@ -120,9 +116,7 @@ class PutProjectMembers(CommandLine):
120
116
  if delete:
121
117
  user_id_list = [e.user_id for e in members]
122
118
  # 自分自身は削除しないようにする
123
- deleted_members = [
124
- e for e in old_project_members if (e["user_id"] not in user_id_list and e["user_id"] != self.service.api.login_user_id)
125
- ]
119
+ deleted_members = [e for e in old_project_members if (e["user_id"] not in user_id_list and e["user_id"] != self.service.api.login_user_id)]
126
120
 
127
121
  count_delete_members = 0
128
122
  logger.info(f"プロジェクト '{project_title}' から、{len(deleted_members)} 件のプロジェクトメンバを削除します。")
@@ -182,9 +176,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
182
176
  ),
183
177
  )
184
178
 
185
- parser.add_argument(
186
- "--delete", action="store_true", help="CSVファイルに記載されていないプロジェクトメンバを削除します。ただし自分自身は削除しません。"
187
- )
179
+ parser.add_argument("--delete", action="store_true", help="CSVファイルに記載されていないプロジェクトメンバを削除します。ただし自分自身は削除しません。")
188
180
 
189
181
  parser.set_defaults(subcommand_func=main)
190
182
 
@@ -70,13 +70,9 @@ def create_replacement_dict(
70
70
  not_masked_biography_set: マスクしないbiographyの集合。指定したbiographyに該当するユーザーのuser_id,username,account_idはマスクしません。
71
71
  """
72
72
 
73
- assert {"user_id", "username", "account_id", "biography"} - set(df_user.columns) == set(), (
74
- "df_userには'user_id','username','account_id','biography'の列が必要です。"
75
- )
73
+ assert {"user_id", "username", "account_id", "biography"} - set(df_user.columns) == set(), "df_userには'user_id','username','account_id','biography'の列が必要です。"
76
74
 
77
- replacement_dict_for_user_id = create_replacement_dict_by_user_id(
78
- df_user, not_masked_biography_set=not_masked_biography_set, not_masked_user_id_set=not_masked_user_id_set
79
- )
75
+ replacement_dict_for_user_id = create_replacement_dict_by_user_id(df_user, not_masked_biography_set=not_masked_biography_set, not_masked_user_id_set=not_masked_user_id_set)
80
76
 
81
77
  df2 = df_user.set_index("user_id")
82
78
  df3 = df2.loc[replacement_dict_for_user_id.keys()]
@@ -181,17 +177,13 @@ def mask_visualization_dir(
181
177
  )
182
178
 
183
179
  # CSVのユーザ情報をマスクする
184
- masked_user_performance = UserPerformance.from_df_wrapper(
185
- masked_worktime_per_date, masked_task_worktime_by_phase_user, task_completion_criteria=project_dir.task_completion_criteria
186
- )
180
+ masked_user_performance = UserPerformance.from_df_wrapper(masked_worktime_per_date, masked_task_worktime_by_phase_user, task_completion_criteria=project_dir.task_completion_criteria)
187
181
  output_project_dir.write_user_performance(masked_user_performance)
188
182
 
189
183
  # メンバのパフォーマンスを散布図で出力する
190
184
  output_project_dir.write_user_performance_scatter_plot(masked_user_performance)
191
185
 
192
- masked_task = project_dir.read_task_list().mask_user_info(
193
- to_replace_for_user_id=replacement_dict.user_id, to_replace_for_username=replacement_dict.username
194
- )
186
+ masked_task = project_dir.read_task_list().mask_user_info(to_replace_for_user_id=replacement_dict.user_id, to_replace_for_username=replacement_dict.username)
195
187
  output_project_dir.write_task_list(masked_task)
196
188
 
197
189
  write_line_graph(masked_task_worktime_by_phase_user, output_project_dir, minimal_output=minimal_output)
@@ -226,9 +218,7 @@ def main(args: argparse.Namespace) -> None:
226
218
  not_masked_biography_set = set(get_list_from_args(args.not_masked_biography)) if args.not_masked_biography is not None else None
227
219
  not_masked_user_id_set = set(get_list_from_args(args.not_masked_user_id)) if args.not_masked_user_id is not None else None
228
220
 
229
- custom_production_volume_list = (
230
- create_custom_production_volume_list(args.custom_production_volume) if args.custom_production_volume is not None else None
231
- )
221
+ custom_production_volume_list = create_custom_production_volume_list(args.custom_production_volume) if args.custom_production_volume is not None else None
232
222
 
233
223
  task_completion_criteria = TaskCompletionCriteria(args.task_completion_criteria)
234
224
  input_project_dir = ProjectDir(
@@ -255,7 +245,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
255
245
  "--dir",
256
246
  type=Path,
257
247
  required=True,
258
- help="マスクしたいプロジェクトディレクトリを指定してください。プロジェクトディレクトリは ``annofabcli statistics visualize`` コマンドの出力結果です。", # noqa: E501
248
+ help="マスクしたいプロジェクトディレクトリを指定してください。プロジェクトディレクトリは ``annofabcli statistics visualize`` コマンドの出力結果です。",
259
249
  )
260
250
 
261
251
  parser.add_argument(
@@ -102,15 +102,9 @@ class WritingVisualizationFile:
102
102
  self.output_project_dir.write_performance_per_started_date_csv(acceptor_per_date_obj, phase=TaskPhase.ACCEPTANCE)
103
103
 
104
104
  # 折れ線グラフを出力
105
- self.output_project_dir.write_performance_line_graph_per_date(
106
- annotator_per_date_obj, phase=TaskPhase.ANNOTATION, user_id_list=self.user_id_list
107
- )
108
- self.output_project_dir.write_performance_line_graph_per_date(
109
- inspector_per_date_obj, phase=TaskPhase.INSPECTION, user_id_list=self.user_id_list
110
- )
111
- self.output_project_dir.write_performance_line_graph_per_date(
112
- acceptor_per_date_obj, phase=TaskPhase.ACCEPTANCE, user_id_list=self.user_id_list
113
- )
105
+ self.output_project_dir.write_performance_line_graph_per_date(annotator_per_date_obj, phase=TaskPhase.ANNOTATION, user_id_list=self.user_id_list)
106
+ self.output_project_dir.write_performance_line_graph_per_date(inspector_per_date_obj, phase=TaskPhase.INSPECTION, user_id_list=self.user_id_list)
107
+ self.output_project_dir.write_performance_line_graph_per_date(acceptor_per_date_obj, phase=TaskPhase.ACCEPTANCE, user_id_list=self.user_id_list)
114
108
 
115
109
  @_catch_exception
116
110
  def write_task_list_and_histogram(self, task: Task) -> None:
@@ -188,9 +182,7 @@ class MergingVisualizationFile:
188
182
  project_info = project_dir.read_project_info()
189
183
  project_info_list.append(project_info)
190
184
 
191
- merge_info = MergingInfo(
192
- target_dir_list=target_dir_list, project_info_list=project_info_list, task_completion_criteria=task_completion_criteria
193
- )
185
+ merge_info = MergingInfo(target_dir_list=target_dir_list, project_info_list=project_info_list, task_completion_criteria=task_completion_criteria)
194
186
  return merge_info
195
187
 
196
188
 
@@ -223,9 +215,7 @@ def merge_visualization_dir( # pylint: disable=too-many-statements
223
215
  worktime_per_date=worktime_per_date,
224
216
  task_completion_criteria=task_completion_criteria,
225
217
  )
226
- writing_obj = WritingVisualizationFile(
227
- output_project_dir, user_id_list=user_id_list, minimal_output=minimal_output, task_completion_criteria=task_completion_criteria
228
- )
218
+ writing_obj = WritingVisualizationFile(output_project_dir, user_id_list=user_id_list, minimal_output=minimal_output, task_completion_criteria=task_completion_criteria)
229
219
 
230
220
  writing_obj.write_task_list_and_histogram(task)
231
221
  writing_obj.write_worktime_per_date(worktime_per_date)
@@ -270,9 +260,7 @@ def main(args: argparse.Namespace) -> None:
270
260
 
271
261
  user_id_list = get_list_from_args(args.user_id) if args.user_id is not None else None
272
262
 
273
- custom_production_volume_list = (
274
- create_custom_production_volume_list(args.custom_production_volume) if args.custom_production_volume is not None else None
275
- )
263
+ custom_production_volume_list = create_custom_production_volume_list(args.custom_production_volume) if args.custom_production_volume is not None else None
276
264
  task_completion_criteria = TaskCompletionCriteria(args.task_completion_criteria)
277
265
  merge_visualization_dir(
278
266
  project_dir_list=[ProjectDir(e, task_completion_criteria) for e in args.dir],
@@ -32,13 +32,9 @@ def create_custom_production_volume_list(cli_value: str) -> list[ProductionVolum
32
32
  def main(args: argparse.Namespace) -> None:
33
33
  root_dir: Path = args.dir
34
34
  # task_completion_criteriaは何でもよいので、とりあえずACCEPTANCE_COMPLETEDを指定
35
- project_dir_list = [
36
- ProjectDir(elm, task_completion_criteria=TaskCompletionCriteria.ACCEPTANCE_COMPLETED) for elm in root_dir.iterdir() if elm.is_dir()
37
- ]
35
+ project_dir_list = [ProjectDir(elm, task_completion_criteria=TaskCompletionCriteria.ACCEPTANCE_COMPLETED) for elm in root_dir.iterdir() if elm.is_dir()]
38
36
 
39
- custom_production_volume_list = (
40
- create_custom_production_volume_list(args.custom_production_volume) if args.custom_production_volume is not None else None
41
- )
37
+ custom_production_volume_list = create_custom_production_volume_list(args.custom_production_volume) if args.custom_production_volume is not None else None
42
38
  project_performance = ProjectPerformance.from_project_dirs(project_dir_list, custom_production_volume_list=custom_production_volume_list)
43
39
  project_performance.to_csv(args.output)
44
40
 
@@ -70,7 +66,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
70
66
 
71
67
  def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
72
68
  subcommand_name = "summarize_whole_performance_csv"
73
- subcommand_help = "``annofabcli statistics visualize`` コマンドの出力結果であるプロジェクトディレクトリから、プロジェクトごとの生産性や品質の一覧を出力します。。" # noqa: E501
69
+ subcommand_help = "``annofabcli statistics visualize`` コマンドの出力結果であるプロジェクトディレクトリから、プロジェクトごとの生産性や品質の一覧を出力します。。"
74
70
  parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help)
75
71
  parse_args(parser)
76
72
  return parser