annofabcli 1.113.0__py3-none-any.whl → 1.114.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.
annofabcli/__main__.py CHANGED
@@ -26,6 +26,7 @@ import annofabcli.stat_visualization.subcommand_stat_visualization
26
26
  import annofabcli.statistics.subcommand_statistics
27
27
  import annofabcli.supplementary.subcommand_supplementary
28
28
  import annofabcli.task.subcommand_task
29
+ import annofabcli.task_count.subcommand_task_count
29
30
  import annofabcli.task_history.subcommand_task_history
30
31
  import annofabcli.task_history_event.subcommand_task_history_event
31
32
 
@@ -121,6 +122,7 @@ def create_parser() -> argparse.ArgumentParser:
121
122
  annofabcli.stat_visualization.subcommand_stat_visualization.add_parser(subparsers)
122
123
  annofabcli.supplementary.subcommand_supplementary.add_parser(subparsers)
123
124
  annofabcli.task.subcommand_task.add_parser(subparsers)
125
+ annofabcli.task_count.subcommand_task_count.add_parser(subparsers)
124
126
  annofabcli.task_history.subcommand_task_history.add_parser(subparsers)
125
127
  annofabcli.task_history_event.subcommand_task_history_event.add_parser(subparsers)
126
128
 
@@ -68,7 +68,7 @@ class DumpAnnotationMain:
68
68
  json_path = task_dir / f"{input_data_id}.json"
69
69
  self.dump_editor_annotation(editor_annotation=editor_annotation, json_path=json_path)
70
70
 
71
- def dump_annotation_for_task(self, task_id: str, output_dir: Path, *, task_index: int | None = None, task_history_index: int | None = None) -> bool:
71
+ def dump_annotation_for_task(self, task_id: str, output_dir: Path, *, task_index: int | None = None, task_history_index: int | None = None, task_history_id: str | None = None) -> bool:
72
72
  """
73
73
  タスク配下のアノテーションをファイルに保存する。
74
74
 
@@ -85,23 +85,23 @@ class DumpAnnotationMain:
85
85
  logger.warning(f"task_id = '{task_id}' のタスクは存在しません。スキップします。")
86
86
  return False
87
87
 
88
- task_history_id: str | None = None
88
+ actual_task_history_id: str | None = task_history_id
89
89
  if task_history_index is not None:
90
90
  task_histories, _ = self.service.api.get_task_histories(self.project_id, task_id)
91
91
  if task_history_index >= len(task_histories):
92
92
  logger.warning(f"task_id='{task_id}' :: task_history_index='{task_history_index}'のタスク履歴は存在しません。タスク履歴は{len(task_histories)}件です。スキップします。")
93
93
  return False
94
- task_history_id = task_histories[task_history_index]["task_history_id"]
94
+ actual_task_history_id = task_histories[task_history_index]["task_history_id"]
95
95
 
96
96
  input_data_id_list = task["input_data_id_list"]
97
97
  task_dir = output_dir / task_id
98
98
  task_dir.mkdir(exist_ok=True, parents=True)
99
- logger.debug(f"{logger_prefix}task_id = '{task_id}' のアノテーション情報を '{task_dir}' ディレクトリに保存します。 :: task_history_id='{task_history_id}'")
99
+ logger.debug(f"{logger_prefix}task_id = '{task_id}' のアノテーション情報を '{task_dir}' ディレクトリに保存します。 :: task_history_id='{actual_task_history_id}'")
100
100
 
101
101
  is_failure = False
102
102
  for input_data_id in input_data_id_list:
103
103
  try:
104
- self.dump_annotation_for_input_data(task_id, input_data_id, task_dir=task_dir, task_history_id=task_history_id)
104
+ self.dump_annotation_for_input_data(task_id, input_data_id, task_dir=task_dir, task_history_id=actual_task_history_id)
105
105
  except Exception:
106
106
  logger.warning(f"タスク'{task_id}', 入力データ'{input_data_id}' のアノテーション情報のダンプに失敗しました。", exc_info=True)
107
107
  is_failure = True
@@ -109,15 +109,15 @@ class DumpAnnotationMain:
109
109
 
110
110
  return not is_failure
111
111
 
112
- def dump_annotation_for_task_wrapper(self, tpl: tuple[int, str], output_dir: Path, *, task_history_index: int | None = None) -> bool:
112
+ def dump_annotation_for_task_wrapper(self, tpl: tuple[int, str], output_dir: Path, *, task_history_index: int | None = None, task_history_id: str | None = None) -> bool:
113
113
  task_index, task_id = tpl
114
114
  try:
115
- return self.dump_annotation_for_task(task_id, output_dir=output_dir, task_index=task_index, task_history_index=task_history_index)
115
+ return self.dump_annotation_for_task(task_id, output_dir=output_dir, task_index=task_index, task_history_index=task_history_index, task_history_id=task_history_id)
116
116
  except Exception: # pylint: disable=broad-except
117
117
  logger.warning(f"タスク'{task_id}'のアノテーション情報のダンプに失敗しました。", exc_info=True)
118
118
  return False
119
119
 
120
- def dump_annotation(self, task_id_list: list[str], output_dir: Path, *, task_history_index: int | None = None, parallelism: int | None = None) -> None:
120
+ def dump_annotation(self, task_id_list: list[str], output_dir: Path, *, task_history_index: int | None = None, task_history_id: str | None = None, parallelism: int | None = None) -> None:
121
121
  project_title = self.facade.get_project_title(self.project_id)
122
122
  logger.info(f"プロジェクト'{project_title}'に対して、タスク{len(task_id_list)} 件のアノテーションをファイルに保存します。")
123
123
 
@@ -126,7 +126,7 @@ class DumpAnnotationMain:
126
126
  success_count = 0
127
127
 
128
128
  if parallelism is not None:
129
- func = functools.partial(self.dump_annotation_for_task_wrapper, task_history_index=task_history_index, output_dir=output_dir)
129
+ func = functools.partial(self.dump_annotation_for_task_wrapper, task_history_index=task_history_index, task_history_id=task_history_id, output_dir=output_dir)
130
130
  with multiprocessing.Pool(parallelism) as pool:
131
131
  result_bool_list = pool.map(func, enumerate(task_id_list))
132
132
  success_count = len([e for e in result_bool_list if e])
@@ -134,7 +134,7 @@ class DumpAnnotationMain:
134
134
  else:
135
135
  for task_index, task_id in enumerate(task_id_list):
136
136
  try:
137
- result = self.dump_annotation_for_task(task_id, output_dir=output_dir, task_history_index=task_history_index, task_index=task_index)
137
+ result = self.dump_annotation_for_task(task_id, output_dir=output_dir, task_history_index=task_history_index, task_history_id=task_history_id, task_index=task_index)
138
138
  if result:
139
139
  success_count += 1
140
140
  except Exception:
@@ -157,7 +157,7 @@ class DumpAnnotation(CommandLine):
157
157
  super().validate_project(project_id, project_member_roles=None)
158
158
 
159
159
  main_obj = DumpAnnotationMain(self.service, project_id)
160
- main_obj.dump_annotation(task_id_list, output_dir=output_dir, parallelism=args.parallelism, task_history_index=args.task_history_index)
160
+ main_obj.dump_annotation(task_id_list, output_dir=output_dir, parallelism=args.parallelism, task_history_index=args.task_history_index, task_history_id=args.task_history_id)
161
161
 
162
162
 
163
163
  def main(args: argparse.Namespace) -> None:
@@ -174,12 +174,19 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
174
174
 
175
175
  parser.add_argument("-o", "--output_dir", type=str, required=True, help="出力先ディレクトリのパス")
176
176
 
177
- parser.add_argument(
177
+ task_history_group = parser.add_mutually_exclusive_group()
178
+ task_history_group.add_argument(
178
179
  "--task_history_index",
179
180
  type=int,
180
181
  help="指定したタスク履歴のインデックス(ゼロ始まり)で付与されたアノテーション情報をダンプします。過去のアノテーション結果をダンプする場合に指定します。"
181
182
  "ただし、過去のアノテーションデータは30日間しか保持されません。30日より前に更新されたアノテーションをダンプした場合は、アノテーションが0件の状態でダンプされます。",
182
183
  )
184
+ task_history_group.add_argument(
185
+ "--task_history_id",
186
+ type=str,
187
+ help="指定したタスク履歴IDで付与されたアノテーション情報をダンプします。過去のアノテーション結果をダンプする場合に指定します。"
188
+ "ただし、過去のアノテーションデータは30日間しか保持されません。30日より前に更新されたアノテーションをダンプした場合は、アノテーションが0件の状態でダンプされます。",
189
+ )
183
190
 
184
191
  parser.add_argument(
185
192
  "--parallelism",
@@ -1,5 +1,6 @@
1
1
  import argparse
2
2
  import logging
3
+ import math
3
4
  import sys
4
5
  import tempfile
5
6
  from collections.abc import Collection
@@ -65,6 +66,9 @@ class Annotation3DBoundingBoxInfo(DataClassJsonMixin):
65
66
  top_z: float
66
67
  """天面のZ座標(location.z + height/2)。回転は考慮していない。"""
67
68
 
69
+ horizontal_distance: float
70
+ """原点(0, 0)からのXY平面上の距離(sqrt(location.x² + location.y²))"""
71
+
68
72
  attributes: dict[str, str | int | bool]
69
73
  """属性情報"""
70
74
 
@@ -96,6 +100,7 @@ def get_annotation_3d_bounding_box_info_list(simple_annotation: dict[str, Any],
96
100
  footprint_area = width * depth
97
101
  bottom_z = location.z - height / 2
98
102
  top_z = location.z + height / 2
103
+ horizontal_distance = math.hypot(location.x, location.y)
99
104
 
100
105
  result.append(
101
106
  Annotation3DBoundingBoxInfo(
@@ -116,6 +121,7 @@ def get_annotation_3d_bounding_box_info_list(simple_annotation: dict[str, Any],
116
121
  footprint_area=footprint_area,
117
122
  bottom_z=bottom_z,
118
123
  top_z=top_z,
124
+ horizontal_distance=horizontal_distance,
119
125
  attributes=detail["attributes"],
120
126
  updated_datetime=simple_annotation["updated_datetime"],
121
127
  )
@@ -177,6 +183,7 @@ def create_df(
177
183
  "footprint_area",
178
184
  "bottom_z",
179
185
  "top_z",
186
+ "horizontal_distance",
180
187
  ]
181
188
 
182
189
  if len(tmp_annotation_bbox_list) == 0:
@@ -39,21 +39,46 @@ class ListAllCommentMain:
39
39
  task_ids: Collection[str] | None,
40
40
  comment_type: CommentType | None,
41
41
  exclude_reply: bool, # noqa: FBT001
42
+ temp_dir: Path | None,
42
43
  ) -> list[dict[str, Any]]:
43
44
  if comment_json is None:
44
45
  downloading_obj = DownloadingFile(self.service)
45
46
  # `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
46
47
  # https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
47
- with tempfile.TemporaryDirectory() as str_temp_dir:
48
- json_path = Path(str_temp_dir) / f"{project_id}__comment.json"
49
- downloading_obj.download_comment_json(project_id, str(json_path))
50
- with json_path.open(encoding="utf-8") as f:
51
- comment_list = json.load(f)
52
-
48
+ if temp_dir is not None:
49
+ json_path = downloading_obj.download_comment_json_to_dir(project_id, temp_dir)
50
+ else:
51
+ with tempfile.TemporaryDirectory() as str_temp_dir:
52
+ json_path = downloading_obj.download_comment_json_to_dir(project_id, Path(str_temp_dir))
53
+ with json_path.open(encoding="utf-8") as f:
54
+ comment_list = json.load(f)
55
+ # 一時ディレクトリの場合はここでフィルタリング処理まで行う
56
+ if task_ids is not None:
57
+ task_id_set = set(task_ids)
58
+ comment_list = [e for e in comment_list if e["task_id"] in task_id_set]
59
+
60
+ if comment_type is not None:
61
+ comment_list = [e for e in comment_list if e["comment_type"] == comment_type.value]
62
+
63
+ # 返信回数を算出する
64
+ reply_counter = create_reply_counter(comment_list)
65
+ for c in comment_list:
66
+ key = (c["task_id"], c["input_data_id"], c["comment_id"])
67
+ c["reply_count"] = reply_counter.get(key, 0)
68
+
69
+ if exclude_reply:
70
+ # 返信コメントを除外する
71
+ comment_list = [e for e in comment_list if e["comment_node"]["_type"] != "Reply"]
72
+
73
+ visualize = AddProps(self.service, project_id)
74
+ comment_list = [visualize.add_properties_to_comment(e) for e in comment_list]
75
+
76
+ return comment_list
53
77
  else:
54
78
  json_path = comment_json
55
- with json_path.open(encoding="utf-8") as f:
56
- comment_list = json.load(f)
79
+
80
+ with json_path.open(encoding="utf-8") as f:
81
+ comment_list = json.load(f)
57
82
 
58
83
  if task_ids is not None:
59
84
  task_id_set = set(task_ids)
@@ -86,6 +111,7 @@ class ListAllComment(CommandLine):
86
111
 
87
112
  task_id_list = annofabcli.common.cli.get_list_from_args(args.task_id) if args.task_id is not None else None
88
113
  comment_type = CommentType(args.comment_type) if args.comment_type is not None else None
114
+ temp_dir = Path(args.temp_dir) if args.temp_dir is not None else None
89
115
 
90
116
  main_obj = ListAllCommentMain(self.service)
91
117
  comment_list = main_obj.get_all_comment(
@@ -94,6 +120,7 @@ class ListAllComment(CommandLine):
94
120
  task_ids=task_id_list,
95
121
  comment_type=comment_type,
96
122
  exclude_reply=args.exclude_reply,
123
+ temp_dir=temp_dir,
97
124
  )
98
125
 
99
126
  logger.info(f"コメントの件数: {len(comment_list)}")
@@ -133,6 +160,12 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
133
160
 
134
161
  parser.add_argument("--exclude_reply", action="store_true", help="返信コメントを除外します。")
135
162
 
163
+ parser.add_argument(
164
+ "--temp_dir",
165
+ type=str,
166
+ help="``--comment_json`` を指定しなかった場合、ダウンロードしたJSONファイルの保存先ディレクトリを指定できます。指定しない場合は、一時ディレクトリに保存されます。",
167
+ )
168
+
136
169
  argument_parser.add_format(
137
170
  choices=[
138
171
  FormatArgument.CSV,
@@ -404,3 +404,72 @@ class DownloadingFile:
404
404
  wait_options=wait_options,
405
405
  )
406
406
  return dest_path
407
+
408
+ def download_task_history_json_to_dir(
409
+ self,
410
+ project_id: str,
411
+ output_dir: Path,
412
+ ) -> Path:
413
+ """
414
+ タスク履歴JSONをoutput_dirに統一された命名規則でダウンロードする。
415
+
416
+ Args:
417
+ project_id: プロジェクトID
418
+ output_dir: 出力ディレクトリ
419
+
420
+ Returns:
421
+ ダウンロードされたファイルのパス
422
+ """
423
+ dest_path = output_dir / get_filename(project_id, "task_history", "json")
424
+
425
+ self.download_task_history_json(
426
+ project_id,
427
+ dest_path=str(dest_path),
428
+ )
429
+ return dest_path
430
+
431
+ def download_task_history_event_json_to_dir(
432
+ self,
433
+ project_id: str,
434
+ output_dir: Path,
435
+ ) -> Path:
436
+ """
437
+ タスク履歴イベントJSONをoutput_dirに統一された命名規則でダウンロードする。
438
+
439
+ Args:
440
+ project_id: プロジェクトID
441
+ output_dir: 出力ディレクトリ
442
+
443
+ Returns:
444
+ ダウンロードされたファイルのパス
445
+ """
446
+ dest_path = output_dir / get_filename(project_id, "task_history_event", "json")
447
+
448
+ self.download_task_history_event_json(
449
+ project_id,
450
+ dest_path=str(dest_path),
451
+ )
452
+ return dest_path
453
+
454
+ def download_comment_json_to_dir(
455
+ self,
456
+ project_id: str,
457
+ output_dir: Path,
458
+ ) -> Path:
459
+ """
460
+ コメントJSONをoutput_dirに統一された命名規則でダウンロードする。
461
+
462
+ Args:
463
+ project_id: プロジェクトID
464
+ output_dir: 出力ディレクトリ
465
+
466
+ Returns:
467
+ ダウンロードされたファイルのパス
468
+ """
469
+ dest_path = output_dir / get_filename(project_id, "comment", "json")
470
+
471
+ self.download_comment_json(
472
+ project_id,
473
+ dest_path=str(dest_path),
474
+ )
475
+ return dest_path
@@ -71,24 +71,39 @@ class ListInputDataWithJsonMain:
71
71
  contain_parent_task_id_list: bool = False,
72
72
  contain_supplementary_data_count: bool = False,
73
73
  is_latest: bool = False,
74
+ temp_dir: Path | None = None,
74
75
  ) -> list[dict[str, Any]]:
75
76
  if input_data_json is None:
76
77
  downloading_obj = DownloadingFile(self.service)
77
78
  # `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
78
79
  # https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
79
- with tempfile.TemporaryDirectory() as str_temp_dir:
80
- json_path = Path(str_temp_dir) / f"{project_id}__input_data.json"
81
- downloading_obj.download_input_data_json(
82
- project_id,
83
- str(json_path),
84
- is_latest=is_latest,
85
- )
86
- with json_path.open(encoding="utf-8") as f:
87
- input_data_list = json.load(f)
80
+ if temp_dir is not None:
81
+ json_path = downloading_obj.download_input_data_json_to_dir(project_id, temp_dir, is_latest=is_latest)
82
+ else:
83
+ with tempfile.TemporaryDirectory() as str_temp_dir:
84
+ json_path = downloading_obj.download_input_data_json_to_dir(project_id, Path(str_temp_dir), is_latest=is_latest)
85
+ with json_path.open(encoding="utf-8") as f:
86
+ input_data_list = json.load(f)
87
+ # 一時ディレクトリの場合はここでフィルタリング処理まで行う
88
+ input_data_id_set = set(input_data_id_list) if input_data_id_list is not None else None
89
+ filtered_input_data_list = [e for e in input_data_list if self.filter_input_data_list(e, input_data_query=input_data_query, input_data_id_set=input_data_id_set)]
90
+
91
+ adding_obj = AddingDetailsToInputData(self.service, project_id)
92
+ if contain_parent_task_id_list:
93
+ adding_obj.add_parent_task_id_list_to_input_data_list(input_data_list)
94
+
95
+ if contain_supplementary_data_count:
96
+ adding_obj.add_supplementary_data_count_to_input_data_list(input_data_list)
97
+
98
+ # 入力データの不要なキーを削除する
99
+ for input_data in input_data_list:
100
+ remove_unnecessary_keys_from_input_data(input_data)
101
+ return filtered_input_data_list
88
102
  else:
89
103
  json_path = input_data_json
90
- with json_path.open(encoding="utf-8") as f:
91
- input_data_list = json.load(f)
104
+
105
+ with json_path.open(encoding="utf-8") as f:
106
+ input_data_list = json.load(f)
92
107
 
93
108
  input_data_id_set = set(input_data_id_list) if input_data_id_list is not None else None
94
109
  filtered_input_data_list = [e for e in input_data_list if self.filter_input_data_list(e, input_data_query=input_data_query, input_data_id_set=input_data_id_set)]
@@ -117,6 +132,7 @@ class ListAllInputData(CommandLine):
117
132
  super().validate_project(project_id, project_member_roles=[ProjectMemberRole.TRAINING_DATA_USER, ProjectMemberRole.OWNER])
118
133
 
119
134
  main_obj = ListInputDataWithJsonMain(self.service)
135
+ temp_dir = Path(args.temp_dir) if args.temp_dir is not None else None
120
136
  input_data_list = main_obj.get_input_data_list(
121
137
  project_id=project_id,
122
138
  input_data_json=args.input_data_json,
@@ -125,6 +141,7 @@ class ListAllInputData(CommandLine):
125
141
  is_latest=args.latest,
126
142
  contain_parent_task_id_list=args.with_parent_task_id_list,
127
143
  contain_supplementary_data_count=args.with_supplementary_data_count,
144
+ temp_dir=temp_dir,
128
145
  )
129
146
 
130
147
  logger.debug(f"入力データ一覧の件数: {len(input_data_list)}")
@@ -195,6 +212,12 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
195
212
 
196
213
  parser.add_argument("--with_supplementary_data_count", action="store_true", help="入力データに紐づく補助情報の個数( ``supplementary_data_count`` )も出力します。")
197
214
 
215
+ parser.add_argument(
216
+ "--temp_dir",
217
+ type=str,
218
+ help="``--input_data_json`` を指定しなかった場合、ダウンロードしたJSONファイルの保存先ディレクトリを指定できます。指定しない場合は、一時ディレクトリに保存されます。",
219
+ )
220
+
198
221
  argument_parser.add_format(
199
222
  choices=[
200
223
  FormatArgument.CSV,
@@ -47,21 +47,34 @@ class ListTasksWithJsonMain:
47
47
  task_id_list: list[str] | None = None,
48
48
  task_query: TaskQuery | None = None,
49
49
  is_latest: bool = False, # noqa: FBT001, FBT002
50
+ temp_dir: Path | None = None,
50
51
  ) -> list[dict[str, Any]]:
51
52
  if task_json is None:
52
53
  downloading_obj = DownloadingFile(self.service)
53
54
  # `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
54
55
  # https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
55
- with tempfile.TemporaryDirectory() as str_temp_dir:
56
- json_path = Path(str_temp_dir) / f"{project_id}__task.json"
57
- downloading_obj.download_task_json(project_id, str(json_path), is_latest=is_latest)
58
- with json_path.open(encoding="utf-8") as f:
59
- task_list = json.load(f)
60
-
56
+ if temp_dir is not None:
57
+ json_path = downloading_obj.download_task_json_to_dir(project_id, temp_dir, is_latest=is_latest)
58
+ else:
59
+ with tempfile.TemporaryDirectory() as str_temp_dir:
60
+ json_path = downloading_obj.download_task_json_to_dir(project_id, Path(str_temp_dir), is_latest=is_latest)
61
+ with json_path.open(encoding="utf-8") as f:
62
+ task_list = json.load(f)
63
+ # 一時ディレクトリの場合はここでフィルタリング処理まで行う
64
+ if task_query is not None:
65
+ task_query = self.facade.set_account_id_of_task_query(project_id, task_query)
66
+
67
+ logger.debug("出力対象のタスクを抽出しています。")
68
+ task_id_set = set(task_id_list) if task_id_list is not None else None
69
+ filtered_task_list = [e for e in task_list if self.match_task_with_conditions(e, task_query=task_query, task_id_set=task_id_set)]
70
+
71
+ visualize_obj = AddProps(self.service, project_id)
72
+ return [visualize_obj.add_properties_to_task(e) for e in filtered_task_list]
61
73
  else:
62
74
  json_path = task_json
63
- with json_path.open(encoding="utf-8") as f:
64
- task_list = json.load(f)
75
+
76
+ with json_path.open(encoding="utf-8") as f:
77
+ task_list = json.load(f)
65
78
 
66
79
  if task_query is not None:
67
80
  task_query = self.facade.set_account_id_of_task_query(project_id, task_query)
@@ -85,12 +98,14 @@ class ListTasksWithJson(CommandLine):
85
98
  super().validate_project(project_id, project_member_roles=None)
86
99
 
87
100
  main_obj = ListTasksWithJsonMain(self.service)
101
+ temp_dir = Path(args.temp_dir) if args.temp_dir is not None else None
88
102
  task_list = main_obj.get_task_list(
89
103
  project_id=project_id,
90
104
  task_json=args.task_json,
91
105
  task_id_list=task_id_list,
92
106
  task_query=task_query,
93
107
  is_latest=args.latest,
108
+ temp_dir=temp_dir,
94
109
  )
95
110
 
96
111
  logger.debug(f"タスク一覧の件数: {len(task_list)}")
@@ -133,6 +148,12 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
133
148
  "指定しない場合は、コマンドを実行した日の02:00(JST)頃のタスクの一覧が出力されます。",
134
149
  )
135
150
 
151
+ parser.add_argument(
152
+ "--temp_dir",
153
+ type=str,
154
+ help="``--task_json`` を指定しなかった場合、ダウンロードしたJSONファイルの保存先ディレクトリを指定できます。指定しない場合は、一時ディレクトリに保存されます。",
155
+ )
156
+
136
157
  argument_parser.add_format(
137
158
  choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON, FormatArgument.TASK_ID_LIST],
138
159
  default=FormatArgument.CSV,
@@ -60,31 +60,35 @@ class ListAllTasksAddedTaskHistoryMain:
60
60
 
61
61
  return task_list
62
62
 
63
- def load_task_list(self, task_json_path: Path | None) -> list[dict[str, Any]]:
64
- if task_json_path is not None:
65
- with task_json_path.open(encoding="utf-8") as f:
66
- return json.load(f)
67
-
68
- # `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
69
- # https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
70
- with tempfile.TemporaryDirectory() as str_temp_dir:
71
- task_json_path = Path(str_temp_dir) / f"{self.project_id}__task.json"
72
- self.downloading_obj.download_task_json(self.project_id, str(task_json_path))
73
- with task_json_path.open(encoding="utf-8") as f:
74
- return json.load(f)
75
-
76
- def load_task_history_dict(self, task_history_json_path: Path | None) -> TaskHistoryDict:
77
- if task_history_json_path is not None:
78
- with task_history_json_path.open(encoding="utf-8") as f:
79
- return json.load(f)
80
- else:
63
+ def load_task_list(self, task_json_path: Path | None, temp_dir: Path | None) -> list[dict[str, Any]]:
64
+ if task_json_path is None:
81
65
  # `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
82
66
  # https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
83
- with tempfile.TemporaryDirectory() as str_temp_dir:
84
- task_history_json_path = Path(str_temp_dir) / f"{self.project_id}__task_history.json"
85
- self.downloading_obj.download_task_history_json(self.project_id, str(task_history_json_path))
86
- with task_history_json_path.open(encoding="utf-8") as f:
87
- return json.load(f)
67
+ if temp_dir is not None:
68
+ task_json_path = self.downloading_obj.download_task_json_to_dir(self.project_id, temp_dir)
69
+ else:
70
+ with tempfile.TemporaryDirectory() as str_temp_dir:
71
+ task_json_path = self.downloading_obj.download_task_json_to_dir(self.project_id, Path(str_temp_dir))
72
+ with task_json_path.open(encoding="utf-8") as f:
73
+ return json.load(f)
74
+
75
+ with task_json_path.open(encoding="utf-8") as f:
76
+ return json.load(f)
77
+
78
+ def load_task_history_dict(self, task_history_json_path: Path | None, temp_dir: Path | None) -> TaskHistoryDict:
79
+ if task_history_json_path is None:
80
+ # `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
81
+ # https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
82
+ if temp_dir is not None:
83
+ task_history_json_path = self.downloading_obj.download_task_history_json_to_dir(self.project_id, temp_dir)
84
+ else:
85
+ with tempfile.TemporaryDirectory() as str_temp_dir:
86
+ task_history_json_path = self.downloading_obj.download_task_history_json_to_dir(self.project_id, Path(str_temp_dir))
87
+ with task_history_json_path.open(encoding="utf-8") as f:
88
+ return json.load(f)
89
+
90
+ with task_history_json_path.open(encoding="utf-8") as f:
91
+ return json.load(f)
88
92
 
89
93
  @staticmethod
90
94
  def match_task_with_conditions(
@@ -120,12 +124,13 @@ class ListAllTasksAddedTaskHistoryMain:
120
124
  task_history_json_path: Path | None,
121
125
  task_id_list: list[str] | None,
122
126
  task_query: TaskQuery | None,
127
+ temp_dir: Path | None,
123
128
  ):
124
129
  """
125
130
  タスク履歴情報を加えたタスク一覧を取得する。
126
131
  """
127
- task_list = self.load_task_list(task_json_path)
128
- task_history_dict = self.load_task_history_dict(task_history_json_path)
132
+ task_list = self.load_task_list(task_json_path, temp_dir)
133
+ task_history_dict = self.load_task_history_dict(task_history_json_path, temp_dir)
129
134
 
130
135
  filtered_task_list = self.filter_task_list(task_list, task_id_list=task_id_list, task_query=task_query)
131
136
 
@@ -163,11 +168,13 @@ class ListAllTasksAddedTaskHistory(CommandLine):
163
168
 
164
169
  self.validate_project(project_id, [ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
165
170
 
171
+ temp_dir = Path(args.temp_dir) if args.temp_dir is not None else None
166
172
  task_list = ListAllTasksAddedTaskHistoryMain(self.service, project_id).get_task_list_added_task_history(
167
173
  task_json_path=args.task_json,
168
174
  task_history_json_path=args.task_history_json,
169
175
  task_id_list=task_id_list,
170
176
  task_query=task_query,
177
+ temp_dir=temp_dir,
171
178
  )
172
179
 
173
180
  logger.info(f"タスク一覧の件数: {len(task_list)}")
@@ -200,6 +207,12 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
200
207
  "JSONファイルは ``$ annofabcli task_history download`` コマンドで取得できます。",
201
208
  )
202
209
 
210
+ parser.add_argument(
211
+ "--temp_dir",
212
+ type=str,
213
+ help="``--task_json`` と ``--task_history_json`` を指定しなかった場合、ダウンロードしたJSONファイルの保存先ディレクトリを指定できます。指定しない場合は、一時ディレクトリに保存されます。",
214
+ )
215
+
203
216
  argument_parser.add_output()
204
217
 
205
218
  argument_parser.add_format(
File without changes