annofabcli 1.114.3__py3-none-any.whl → 1.114.5__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 (62) hide show
  1. annofabcli/annotation/list_annotation.py +4 -4
  2. annofabcli/annotation/list_annotation_count.py +4 -4
  3. annofabcli/annotation_specs/export_annotation_specs.py +4 -4
  4. annofabcli/annotation_specs/get_annotation_specs_with_attribute_id_replaced.py +4 -4
  5. annofabcli/annotation_specs/get_annotation_specs_with_choice_id_replaced.py +4 -4
  6. annofabcli/annotation_specs/get_annotation_specs_with_label_id_replaced.py +4 -4
  7. annofabcli/annotation_specs/list_annotation_specs_attribute.py +7 -7
  8. annofabcli/annotation_specs/list_annotation_specs_choice.py +9 -9
  9. annofabcli/annotation_specs/list_annotation_specs_history.py +2 -2
  10. annofabcli/annotation_specs/list_annotation_specs_label.py +8 -8
  11. annofabcli/annotation_specs/list_annotation_specs_label_attribute.py +7 -7
  12. annofabcli/annotation_specs/list_label_color.py +2 -2
  13. annofabcli/annotation_zip/list_annotation_3d_bounding_box.py +8 -8
  14. annofabcli/annotation_zip/list_annotation_bounding_box_2d.py +8 -8
  15. annofabcli/annotation_zip/list_polygon_annotation.py +8 -8
  16. annofabcli/annotation_zip/list_polyline_annotation.py +8 -8
  17. annofabcli/annotation_zip/list_range_annotation.py +8 -8
  18. annofabcli/annotation_zip/list_single_point_annotation.py +8 -8
  19. annofabcli/annotation_zip/validate_annotation.py +2 -2
  20. annofabcli/comment/list_all_comment.py +8 -8
  21. annofabcli/comment/list_comment.py +8 -8
  22. annofabcli/common/cli.py +3 -3
  23. annofabcli/common/enums.py +1 -1
  24. annofabcli/common/pandas.py +4 -3
  25. annofabcli/common/utils.py +10 -10
  26. annofabcli/common/visualize.py +3 -3
  27. annofabcli/filesystem/mask_user_info.py +3 -2
  28. annofabcli/input_data/list_all_input_data.py +11 -22
  29. annofabcli/input_data/list_all_input_data_merged_task.py +7 -7
  30. annofabcli/input_data/list_input_data.py +67 -18
  31. annofabcli/instruction/list_instruction_history.py +2 -2
  32. annofabcli/job/list_job.py +2 -2
  33. annofabcli/job/list_last_job.py +2 -2
  34. annofabcli/my_account/get_my_account.py +4 -4
  35. annofabcli/organization/list_organization.py +5 -5
  36. annofabcli/organization_member/list_organization_member.py +4 -4
  37. annofabcli/project/list_project.py +9 -9
  38. annofabcli/project_member/list_users.py +4 -4
  39. annofabcli/statistics/list_annotation_area.py +8 -8
  40. annofabcli/statistics/list_annotation_attribute.py +8 -8
  41. annofabcli/statistics/list_annotation_attribute_filled_count.py +11 -11
  42. annofabcli/statistics/list_annotation_count.py +529 -166
  43. annofabcli/statistics/list_annotation_duration.py +8 -8
  44. annofabcli/statistics/list_video_duration.py +6 -6
  45. annofabcli/statistics/summarize_task_count_by_task_id_group.py +2 -2
  46. annofabcli/statistics/summarize_task_count_by_user.py +2 -2
  47. annofabcli/statistics/visualization/dataframe/project_performance.py +2 -1
  48. annofabcli/statistics/visualization/dataframe/task.py +25 -6
  49. annofabcli/supplementary/list_supplementary_data.py +2 -2
  50. annofabcli/task/list_all_tasks.py +8 -18
  51. annofabcli/task/list_all_tasks_added_task_history.py +6 -6
  52. annofabcli/task/list_tasks.py +73 -37
  53. annofabcli/task/list_tasks_added_task_history.py +25 -16
  54. annofabcli/task_history/list_all_task_history.py +6 -6
  55. annofabcli/task_history/list_task_history.py +6 -6
  56. annofabcli/task_history_event/list_all_task_history_event.py +6 -6
  57. annofabcli/task_history_event/list_worktime.py +6 -6
  58. {annofabcli-1.114.3.dist-info → annofabcli-1.114.5.dist-info}/METADATA +2 -2
  59. {annofabcli-1.114.3.dist-info → annofabcli-1.114.5.dist-info}/RECORD +62 -62
  60. {annofabcli-1.114.3.dist-info → annofabcli-1.114.5.dist-info}/WHEEL +0 -0
  61. {annofabcli-1.114.3.dist-info → annofabcli-1.114.5.dist-info}/entry_points.txt +0 -0
  62. {annofabcli-1.114.3.dist-info → annofabcli-1.114.5.dist-info}/licenses/LICENSE +0 -0
@@ -34,7 +34,7 @@ from annofabcli.common.cli import (
34
34
  build_annofabapi_resource_and_login,
35
35
  )
36
36
  from annofabcli.common.download import DownloadingFile
37
- from annofabcli.common.enums import FormatArgument
37
+ from annofabcli.common.enums import OutputFormat
38
38
  from annofabcli.common.facade import (
39
39
  AnnofabApiFacade,
40
40
  TaskQuery,
@@ -504,7 +504,7 @@ class ListAnnotationDurationMain:
504
504
  self,
505
505
  annotation_path: Path,
506
506
  output_file: Path,
507
- arg_format: FormatArgument,
507
+ arg_format: OutputFormat,
508
508
  *,
509
509
  project_id: str | None = None,
510
510
  input_data_json_path: Path | None = None,
@@ -527,12 +527,12 @@ class ListAnnotationDurationMain:
527
527
 
528
528
  logger.info(f"{len(annotation_duration_list)} 件のタスクに含まれる区間アノテーションの長さ情報を出力します。")
529
529
 
530
- if arg_format == FormatArgument.CSV:
530
+ if arg_format == OutputFormat.CSV:
531
531
  assert csv_type is not None
532
532
  self.print_annotation_duration_csv(annotation_duration_list, output_file=output_file, csv_type=csv_type, annotation_specs=annotation_specs)
533
533
 
534
- elif arg_format in [FormatArgument.PRETTY_JSON, FormatArgument.JSON]:
535
- json_is_pretty = arg_format == FormatArgument.PRETTY_JSON
534
+ elif arg_format in [OutputFormat.PRETTY_JSON, OutputFormat.JSON]:
535
+ json_is_pretty = arg_format == OutputFormat.PRETTY_JSON
536
536
 
537
537
  print_json(
538
538
  [e.to_dict(encode_json=True) for e in annotation_duration_list],
@@ -578,7 +578,7 @@ class ListAnnotationDuration(CommandLine):
578
578
 
579
579
  csv_type = CsvType(args.type)
580
580
  output_file: Path = args.output
581
- arg_format = FormatArgument(args.format)
581
+ arg_format = OutputFormat(args.format)
582
582
  main_obj = ListAnnotationDurationMain(self.service)
583
583
 
584
584
  downloading_obj = DownloadingFile(self.service)
@@ -660,8 +660,8 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
660
660
  )
661
661
 
662
662
  argument_parser.add_format(
663
- choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON],
664
- default=FormatArgument.CSV,
663
+ choices=[OutputFormat.CSV, OutputFormat.JSON, OutputFormat.PRETTY_JSON],
664
+ default=OutputFormat.CSV,
665
665
  )
666
666
 
667
667
  argument_parser.add_output()
@@ -20,7 +20,7 @@ from annofabcli.common.cli import (
20
20
  build_annofabapi_resource_and_login,
21
21
  )
22
22
  from annofabcli.common.download import DownloadingFile
23
- from annofabcli.common.enums import FormatArgument
23
+ from annofabcli.common.enums import OutputFormat
24
24
  from annofabcli.common.facade import AnnofabApiFacade
25
25
  from annofabcli.common.utils import print_according_to_format, print_csv
26
26
 
@@ -81,7 +81,7 @@ class ListVideoDuration(CommandLine):
81
81
  self,
82
82
  task_json: Path,
83
83
  input_data_json: Path,
84
- output_format: FormatArgument,
84
+ output_format: OutputFormat,
85
85
  output_file: Path | None,
86
86
  ) -> None:
87
87
  with task_json.open(encoding="utf-8") as f:
@@ -91,7 +91,7 @@ class ListVideoDuration(CommandLine):
91
91
 
92
92
  video_duration_list = get_video_duration_list(task_list=task_list, input_data_list=input_data_list)
93
93
  logger.info(f"{len(video_duration_list)} 件のタスクの動画長さを出力します。")
94
- if output_format == FormatArgument.CSV:
94
+ if output_format == OutputFormat.CSV:
95
95
  columns = [
96
96
  "project_id",
97
97
  "task_id",
@@ -125,7 +125,7 @@ class ListVideoDuration(CommandLine):
125
125
  )
126
126
  sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
127
127
 
128
- func = partial(self.list_video_duration, output_file=args.output, output_format=FormatArgument(args.format))
128
+ func = partial(self.list_video_duration, output_file=args.output, output_format=OutputFormat(args.format))
129
129
 
130
130
  def wrapper_func(temp_dir: Path) -> None:
131
131
  downloading_obj = DownloadingFile(self.service)
@@ -190,8 +190,8 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
190
190
  )
191
191
 
192
192
  argument_parser.add_format(
193
- choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON],
194
- default=FormatArgument.CSV,
193
+ choices=[OutputFormat.CSV, OutputFormat.JSON, OutputFormat.PRETTY_JSON],
194
+ default=OutputFormat.CSV,
195
195
  )
196
196
 
197
197
  argument_parser.add_output()
@@ -19,7 +19,7 @@ from annofabcli.common.cli import (
19
19
  )
20
20
  from annofabcli.common.dataclasses import WaitOptions
21
21
  from annofabcli.common.download import DownloadingFile
22
- from annofabcli.common.enums import FormatArgument
22
+ from annofabcli.common.enums import OutputFormat
23
23
  from annofabcli.common.facade import AnnofabApiFacade
24
24
  from annofabcli.statistics.summarize_task_count import get_step_for_current_phase
25
25
 
@@ -144,7 +144,7 @@ class SummarizeTaskCountByTaskId(CommandLine):
144
144
  columns = ["task_id_group"] + [status.value for status in TaskStatusForSummary] + ["sum"]
145
145
  annofabcli.common.utils.print_according_to_format(
146
146
  df[columns],
147
- format=FormatArgument(FormatArgument.CSV),
147
+ format=OutputFormat.CSV,
148
148
  output=self.output,
149
149
  )
150
150
 
@@ -16,7 +16,7 @@ from annofabcli.common.cli import (
16
16
  )
17
17
  from annofabcli.common.dataclasses import WaitOptions
18
18
  from annofabcli.common.download import DownloadingFile
19
- from annofabcli.common.enums import FormatArgument
19
+ from annofabcli.common.enums import OutputFormat
20
20
  from annofabcli.common.facade import AnnofabApiFacade
21
21
 
22
22
  logger = logging.getLogger(__name__)
@@ -112,7 +112,7 @@ class SummarizeTaskCountByUser(CommandLine):
112
112
  target_df = df[columns].sort_values("user_id")
113
113
  annofabcli.common.utils.print_according_to_format(
114
114
  target_df,
115
- format=FormatArgument(FormatArgument.CSV),
115
+ format=OutputFormat.CSV,
116
116
  output=self.output,
117
117
  )
118
118
 
@@ -136,7 +136,8 @@ class ProjectWorktimePerMonth:
136
136
  series = df.groupby(pandas.Grouper(key="dt_date", freq=get_frequency_of_monthend())).sum(numeric_only=True)[worktime_column.value]
137
137
  # indexを"2022-04"という形式にする
138
138
  new_index = [str(dt)[0:7] for dt in series.index]
139
- result = pandas.Series(series.values, index=new_index)
139
+ # pandas 3.0対応: .valuesではなく.to_numpy()を使用
140
+ result = pandas.Series(series.to_numpy(), index=new_index)
140
141
  result["dirname"] = project_dir.project_dir.name
141
142
  result["project_title"] = project_dir.get_project_title()
142
143
  return result
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import datetime
3
4
  import logging
4
5
  from pathlib import Path
5
6
  from typing import Any
@@ -9,7 +10,6 @@ import bokeh
9
10
  import bokeh.layouts
10
11
  import numpy
11
12
  import pandas
12
- import pytz
13
13
  from bokeh.plotting import figure
14
14
 
15
15
  from annofabcli.common.bokeh import convert_1d_figure_list_to_2d, create_pretext_from_metadata
@@ -426,10 +426,11 @@ class Task:
426
426
  # タイムゾーンを指定している理由::
427
427
  # すべてがNaNのseriesをdatetimeに変換すると、型にタイムゾーンが指定されない。
428
428
  # その状態で加算すると、`TypeError: DatetimeArray subtraction must have the same timezones or no timezones`というエラーが発生するため
429
+ # pandas 3.0対応: pytz.FixedOffsetの代わりにdatetime.timezoneを使用
429
430
  if not isinstance(dt1.dtype, pandas.DatetimeTZDtype):
430
- dt1 = dt1.dt.tz_localize(pytz.FixedOffset(540))
431
+ dt1 = dt1.dt.tz_localize(datetime.timezone(datetime.timedelta(hours=9)))
431
432
  if not isinstance(dt2.dtype, pandas.DatetimeTZDtype):
432
- dt2 = dt2.dt.tz_localize(pytz.FixedOffset(540))
433
+ dt2 = dt2.dt.tz_localize(datetime.timezone(datetime.timedelta(hours=9)))
433
434
 
434
435
  return (dt1 - dt2).dt.total_seconds() / 3600 / 24
435
436
 
@@ -532,9 +533,27 @@ class Task:
532
533
  if not self._validate_df_for_output(output_file):
533
534
  return
534
535
 
535
- existing_optional_columns = [col for col in self.optional_columns if col in set(self.df.columns)]
536
- columns = self.required_columns + existing_optional_columns
537
- print_csv(self.df[columns], str(output_file))
536
+ # metadata列が存在する場合は展開する
537
+ if "metadata" in self.df.columns:
538
+ # metadata列のみをjson_normalizeで展開
539
+ df_metadata = pandas.json_normalize(self.df["metadata"].tolist())
540
+ # 列名にmetadata.プレフィックスを追加
541
+ df_metadata.columns = [f"metadata.{col}" for col in df_metadata.columns]
542
+ # 元のDataFrameからmetadata列を削除
543
+ df_without_metadata = self.df.drop(columns=["metadata"])
544
+ # 展開したmetadata列を結合
545
+ df_normalized = pandas.concat([df_without_metadata.reset_index(drop=True), df_metadata.reset_index(drop=True)], axis=1)
546
+ else:
547
+ df_normalized = self.df
548
+
549
+ # metadata.*列を検出
550
+ metadata_columns = sorted([col for col in df_normalized.columns if col.startswith("metadata.")])
551
+
552
+ # 出力対象の列を定義
553
+ existing_optional_columns = [col for col in self.optional_columns if col in set(df_normalized.columns)]
554
+ columns = self.required_columns + existing_optional_columns + metadata_columns
555
+
556
+ print_csv(df_normalized[columns], str(output_file))
538
557
 
539
558
  def mask_user_info(
540
559
  self,
@@ -15,7 +15,7 @@ from annofabapi.models import SupplementaryData
15
15
  import annofabcli.common.cli
16
16
  from annofabcli.common.cli import PARALLELISM_CHOICES, ArgumentParser, CommandLine, build_annofabapi_resource_and_login
17
17
  from annofabcli.common.download import DownloadingFile
18
- from annofabcli.common.enums import FormatArgument
18
+ from annofabcli.common.enums import OutputFormat
19
19
  from annofabcli.common.facade import AnnofabApiFacade
20
20
 
21
21
  logger = logging.getLogger(__name__)
@@ -155,7 +155,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
155
155
  ),
156
156
  )
157
157
 
158
- argument_parser.add_format(choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON], default=FormatArgument.CSV)
158
+ argument_parser.add_format(choices=[OutputFormat.CSV, OutputFormat.JSON, OutputFormat.PRETTY_JSON], default=OutputFormat.CSV)
159
159
  argument_parser.add_output()
160
160
 
161
161
  parser.add_argument(
@@ -6,17 +6,15 @@ from pathlib import Path
6
6
  from typing import Any
7
7
 
8
8
  import annofabapi
9
- import pandas
10
9
  from annofabapi.dataclass.task import Task
11
10
 
12
11
  import annofabcli.common.cli
13
12
  from annofabcli.common.cli import ArgumentParser, CommandLine, build_annofabapi_resource_and_login
14
13
  from annofabcli.common.download import DownloadingFile
15
- from annofabcli.common.enums import FormatArgument
14
+ from annofabcli.common.enums import OutputFormat
16
15
  from annofabcli.common.facade import AnnofabApiFacade, TaskQuery, match_task_with_query
17
- from annofabcli.common.utils import get_columns_with_priority
18
16
  from annofabcli.common.visualize import AddProps
19
- from annofabcli.task.list_tasks import ListTasks
17
+ from annofabcli.task.list_tasks import print_task_list
20
18
 
21
19
  logger = logging.getLogger(__name__)
22
20
 
@@ -44,9 +42,10 @@ class ListTasksWithJsonMain:
44
42
  self,
45
43
  project_id: str,
46
44
  task_json: Path | None,
45
+ *,
47
46
  task_id_list: list[str] | None = None,
48
47
  task_query: TaskQuery | None = None,
49
- is_latest: bool = False, # noqa: FBT001, FBT002
48
+ is_latest: bool = False,
50
49
  temp_dir: Path | None = None,
51
50
  ) -> list[dict[str, Any]]:
52
51
  if task_json is None:
@@ -108,17 +107,8 @@ class ListTasksWithJson(CommandLine):
108
107
  temp_dir=temp_dir,
109
108
  )
110
109
 
111
- logger.debug(f"タスク一覧の件数: {len(task_list)}")
112
-
113
- if len(task_list) > 0:
114
- if self.str_format == FormatArgument.CSV.value:
115
- df = pandas.DataFrame(task_list)
116
- columns = get_columns_with_priority(df, prior_columns=ListTasks.PRIOR_COLUMNS)
117
- self.print_csv(df[columns])
118
- else:
119
- self.print_according_to_format(task_list)
120
- else:
121
- logger.info("タスク一覧の件数が0件のため、出力しません。")
110
+ logger.info(f"{len(task_list)}件のタスク情報を出力します。")
111
+ print_task_list(task_list, OutputFormat(args.format), args.output)
122
112
 
123
113
 
124
114
  def main(args: argparse.Namespace) -> None:
@@ -155,8 +145,8 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
155
145
  )
156
146
 
157
147
  argument_parser.add_format(
158
- choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON, FormatArgument.TASK_ID_LIST],
159
- default=FormatArgument.CSV,
148
+ choices=[OutputFormat.CSV, OutputFormat.JSON, OutputFormat.PRETTY_JSON, OutputFormat.TASK_ID_LIST],
149
+ default=OutputFormat.CSV,
160
150
  )
161
151
  argument_parser.add_output()
162
152
 
@@ -21,7 +21,7 @@ from annofabcli.common.cli import (
21
21
  )
22
22
  from annofabcli.common.dataclasses import WaitOptions
23
23
  from annofabcli.common.download import DownloadingFile
24
- from annofabcli.common.enums import FormatArgument
24
+ from annofabcli.common.enums import OutputFormat
25
25
  from annofabcli.common.facade import AnnofabApiFacade, TaskQuery, match_task_with_query
26
26
  from annofabcli.task.list_tasks_added_task_history import AddingAdditionalInfoToTask, TasksAddedTaskHistoryOutput
27
27
 
@@ -118,14 +118,14 @@ class ListAllTasksAddedTaskHistoryMain:
118
118
  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)]
119
119
  return filtered_task_list
120
120
 
121
- def get_task_list_added_task_history( # noqa: ANN201
121
+ def get_task_list_added_task_history(
122
122
  self,
123
123
  task_json_path: Path | None,
124
124
  task_history_json_path: Path | None,
125
125
  task_id_list: list[str] | None,
126
126
  task_query: TaskQuery | None,
127
127
  temp_dir: Path | None,
128
- ):
128
+ ) -> list[dict[str, Any]]:
129
129
  """
130
130
  タスク履歴情報を加えたタスク一覧を取得する。
131
131
  """
@@ -178,7 +178,7 @@ class ListAllTasksAddedTaskHistory(CommandLine):
178
178
  )
179
179
 
180
180
  logger.info(f"タスク一覧の件数: {len(task_list)}")
181
- TasksAddedTaskHistoryOutput(task_list).output(output_path=args.output, output_format=FormatArgument(args.format))
181
+ TasksAddedTaskHistoryOutput(task_list).output(output_path=args.output, output_format=OutputFormat(args.format))
182
182
 
183
183
 
184
184
  def main(args: argparse.Namespace) -> None:
@@ -216,8 +216,8 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
216
216
  argument_parser.add_output()
217
217
 
218
218
  argument_parser.add_format(
219
- choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON],
220
- default=FormatArgument.CSV,
219
+ choices=[OutputFormat.CSV, OutputFormat.JSON, OutputFormat.PRETTY_JSON],
220
+ default=OutputFormat.CSV,
221
221
  )
222
222
 
223
223
  parser.set_defaults(subcommand_func=main)
@@ -1,5 +1,6 @@
1
1
  import argparse
2
2
  import logging
3
+ from pathlib import Path
3
4
  from typing import Any
4
5
 
5
6
  import annofabapi
@@ -8,14 +9,77 @@ from annofabapi.models import Task
8
9
 
9
10
  import annofabcli.common.cli
10
11
  from annofabcli.common.cli import ArgumentParser, CommandLine, build_annofabapi_resource_and_login
11
- from annofabcli.common.enums import FormatArgument
12
+ from annofabcli.common.enums import OutputFormat
12
13
  from annofabcli.common.facade import AnnofabApiFacade
13
- from annofabcli.common.utils import get_columns_with_priority
14
+ from annofabcli.common.utils import get_columns_with_priority, print_csv, print_id_list, print_json
14
15
  from annofabcli.common.visualize import AddProps
15
16
 
16
17
  logger = logging.getLogger(__name__)
17
18
 
18
19
 
20
+ def print_task_list(
21
+ task_list: list[dict[str, Any]],
22
+ output_format: OutputFormat,
23
+ output_file: Path | None,
24
+ ) -> None:
25
+ """
26
+ タスク一覧を指定されたフォーマットで出力する。
27
+
28
+ Args:
29
+ task_list: タスク一覧
30
+ output_format: 出力フォーマット
31
+ output_file: 出力先
32
+ """
33
+ task_prior_columns = [
34
+ "project_id",
35
+ "task_id",
36
+ "phase",
37
+ "phase_stage",
38
+ "status",
39
+ "started_datetime",
40
+ "updated_datetime",
41
+ "operation_updated_datetime",
42
+ "account_id",
43
+ "user_id",
44
+ "username",
45
+ "worktime_hour",
46
+ "number_of_rejections_by_inspection",
47
+ "number_of_rejections_by_acceptance",
48
+ "sampling",
49
+ "input_data_count",
50
+ ]
51
+
52
+ if output_format == OutputFormat.CSV:
53
+ if len(task_list) > 0:
54
+ # json_normalizeでメタデータを自動展開
55
+ df = pandas.json_normalize(task_list)
56
+
57
+ # metadata.*列を検出して優先列リストに追加
58
+ metadata_columns = sorted([col for col in df.columns if col.startswith("metadata.")])
59
+ prior_columns_with_metadata = task_prior_columns + metadata_columns
60
+ columns = get_columns_with_priority(df, prior_columns=prior_columns_with_metadata)
61
+ # work_time_span列を除外(worktime_hourと重複するため)
62
+ # histories_by_phase列を除外(list型のためCSVでは扱いにくいため)
63
+ # input_data_id_list列を除外(list型のためCSVでは扱いにくいため。input_data_countで件数は把握できる)
64
+ columns = [col for col in columns if col not in ["work_time_span", "histories_by_phase", "input_data_id_list"]]
65
+ print_csv(df[columns], output=output_file)
66
+ else:
67
+ df = pandas.DataFrame(columns=task_prior_columns)
68
+ print_csv(df, output=output_file)
69
+
70
+ elif output_format == OutputFormat.PRETTY_JSON:
71
+ print_json(task_list, is_pretty=True, output=output_file)
72
+
73
+ elif output_format == OutputFormat.JSON:
74
+ print_json(task_list, is_pretty=False, output=output_file)
75
+
76
+ elif output_format == OutputFormat.TASK_ID_LIST:
77
+ task_id_list = [e["task_id"] for e in task_list]
78
+ print_id_list(task_id_list, output=output_file)
79
+ else:
80
+ raise ValueError(f"{output_format}は対応していないフォーマットです。")
81
+
82
+
19
83
  class ListTasksMain:
20
84
  def __init__(self, service: annofabapi.Resource, project_id: str) -> None:
21
85
  self.service = service
@@ -53,7 +117,7 @@ class ListTasksMain:
53
117
 
54
118
  """
55
119
 
56
- def remove_key(arg_key: str): # noqa: ANN202
120
+ def remove_key(arg_key: str) -> None:
57
121
  if arg_key in task_query:
58
122
  logger.info(f"タスク検索クエリから、`{arg_key}` キーを削除しました。")
59
123
  task_query.pop(arg_key)
@@ -96,7 +160,6 @@ class ListTasksMain:
96
160
  task_query = {}
97
161
 
98
162
  if user_id_list is None:
99
- logger.debug(f"task_query: {task_query}")
100
163
  tasks = self.service.wrapper.get_all_tasks(project_id, query_params=task_query)
101
164
  if len(tasks) == 10000:
102
165
  logger.warning("タスク一覧は10,000件で打ち切られている可能性があります。")
@@ -151,27 +214,6 @@ class ListTasks(CommandLine):
151
214
  super().__init__(service, facade, args)
152
215
  self.visualize = AddProps(self.service, args.project_id)
153
216
 
154
- PRIOR_COLUMNS = [ # noqa: RUF012
155
- "project_id",
156
- "task_id",
157
- "phase",
158
- "phase_stage",
159
- "status",
160
- "started_datetime",
161
- "updated_datetime",
162
- "operation_updated_datetime",
163
- "account_id",
164
- "user_id",
165
- "username",
166
- "worktime_hour",
167
- "number_of_rejections_by_inspection",
168
- "number_of_rejections_by_acceptance",
169
- "metadata",
170
- "sampling",
171
- "input_data_count",
172
- "input_data_id_list",
173
- ]
174
-
175
217
  def main(self) -> None:
176
218
  args = self.args
177
219
 
@@ -190,17 +232,11 @@ class ListTasks(CommandLine):
190
232
  user_id_list=user_id_list,
191
233
  )
192
234
 
193
- logger.debug(f"タスク一覧の件数: {len(task_list)}")
235
+ logger.info(f"{len(task_list)}件のタスク情報を出力します。")
194
236
 
195
- if len(task_list) > 0:
196
- if self.str_format == FormatArgument.CSV.value:
197
- df = pandas.DataFrame(task_list)
198
- columns = get_columns_with_priority(df, prior_columns=self.PRIOR_COLUMNS)
199
- self.print_csv(df[columns])
200
- else:
201
- self.print_according_to_format(task_list)
202
- else:
203
- logger.info("タスク一覧の件数が0件のため、出力しません。")
237
+ output_file = args.output
238
+ output_format = OutputFormat(args.format)
239
+ print_task_list(task_list, output_format, output_file)
204
240
 
205
241
 
206
242
  def main(args: argparse.Namespace) -> None:
@@ -245,8 +281,8 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
245
281
  )
246
282
 
247
283
  argument_parser.add_format(
248
- choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON, FormatArgument.TASK_ID_LIST],
249
- default=FormatArgument.CSV,
284
+ choices=[OutputFormat.CSV, OutputFormat.JSON, OutputFormat.PRETTY_JSON, OutputFormat.TASK_ID_LIST],
285
+ default=OutputFormat.CSV,
250
286
  )
251
287
  argument_parser.add_output()
252
288
 
@@ -14,7 +14,7 @@ from annofabapi.utils import get_task_history_index_skipped_acceptance, get_task
14
14
 
15
15
  import annofabcli.common.cli
16
16
  from annofabcli.common.cli import ArgumentParser, CommandLine, build_annofabapi_resource_and_login
17
- from annofabcli.common.enums import FormatArgument
17
+ from annofabcli.common.enums import OutputFormat
18
18
  from annofabcli.common.facade import AnnofabApiFacade
19
19
  from annofabcli.common.utils import isoduration_to_hour, print_csv, print_json
20
20
  from annofabcli.common.visualize import AddProps
@@ -384,7 +384,7 @@ class ListTasksAddedTaskHistoryMain:
384
384
 
385
385
  for index, task in enumerate(task_list):
386
386
  if (index + 1) % 100 == 0:
387
- logger.debug(f"{index + 1} 件目のタスク履歴情報を取得します。")
387
+ logger.info(f"{index + 1} 件目のタスク履歴情報を取得します。")
388
388
 
389
389
  obj.add_additional_info_to_task(task)
390
390
  task_id = task["task_id"]
@@ -422,7 +422,6 @@ class TasksAddedTaskHistoryOutput:
422
422
  "user_id",
423
423
  "username",
424
424
  "input_data_count",
425
- "metadata",
426
425
  "sampling",
427
426
  # 作業時間情報
428
427
  "worktime_hour",
@@ -454,20 +453,30 @@ class TasksAddedTaskHistoryOutput:
454
453
  ]
455
454
  )
456
455
 
457
- def output(self, output_path: Path, output_format: FormatArgument) -> None:
456
+ def output(self, output_path: Path, output_format: OutputFormat) -> None:
458
457
  task_list = self.task_list
459
458
  logger.debug(f"タスク {len(task_list)} 件の情報を出力します。")
460
- if output_format == FormatArgument.CSV:
461
- df_task = pandas.DataFrame(task_list, columns=self._get_output_target_columns())
462
- print_csv(
463
- df_task[self._get_output_target_columns()],
464
- output=output_path,
465
- )
466
-
467
- elif output_format == FormatArgument.JSON:
459
+ if output_format == OutputFormat.CSV:
460
+ if len(task_list) > 0:
461
+ # json_normalizeでメタデータを自動展開
462
+ df = pandas.json_normalize(task_list)
463
+
464
+ # metadata.*列を検出して出力対象列リストに追加
465
+ metadata_columns = sorted([col for col in df.columns if col.startswith("metadata.")])
466
+ output_columns = self._get_output_target_columns() + metadata_columns
467
+ # 出力列を output_columns に含まれる列のみに限定(意図しない列の混入を防ぐ)
468
+ columns = [col for col in output_columns if col in df.columns]
469
+ print_csv(df[columns], output=output_path)
470
+ else:
471
+ df = pandas.DataFrame(columns=self._get_output_target_columns())
472
+ print_csv(df, output=output_path)
473
+
474
+ elif output_format == OutputFormat.JSON:
468
475
  print_json(task_list, is_pretty=False, output=output_path)
469
- elif output_format == FormatArgument.PRETTY_JSON:
476
+ elif output_format == OutputFormat.PRETTY_JSON:
470
477
  print_json(task_list, is_pretty=True, output=output_path)
478
+ else:
479
+ raise ValueError(f"{output_format=}は不正な値です。")
471
480
 
472
481
 
473
482
  class ListTasksAddedTaskHistory(CommandLine):
@@ -481,7 +490,7 @@ class ListTasksAddedTaskHistory(CommandLine):
481
490
  task_list = main_obj.main(task_query=task_query, task_id_list=task_id_list)
482
491
 
483
492
  output_obj = TasksAddedTaskHistoryOutput(task_list)
484
- output_obj.output(args.output, output_format=FormatArgument(args.format))
493
+ output_obj.output(args.output, output_format=OutputFormat(args.format))
485
494
 
486
495
 
487
496
  def main(args: argparse.Namespace) -> None:
@@ -519,8 +528,8 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
519
528
  argument_parser.add_output()
520
529
 
521
530
  argument_parser.add_format(
522
- choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON],
523
- default=FormatArgument.CSV,
531
+ choices=[OutputFormat.CSV, OutputFormat.JSON, OutputFormat.PRETTY_JSON],
532
+ default=OutputFormat.CSV,
524
533
  )
525
534
 
526
535
  parser.set_defaults(subcommand_func=main)
@@ -10,7 +10,7 @@ from annofabapi.models import TaskHistory
10
10
  import annofabcli.common.cli
11
11
  from annofabcli.common.cli import ArgumentParser, CommandLine, build_annofabapi_resource_and_login
12
12
  from annofabcli.common.download import DownloadingFile
13
- from annofabcli.common.enums import FormatArgument
13
+ from annofabcli.common.enums import OutputFormat
14
14
  from annofabcli.common.facade import AnnofabApiFacade
15
15
  from annofabcli.common.visualize import AddProps
16
16
 
@@ -92,7 +92,7 @@ class ListTaskHistoryWithJson(CommandLine):
92
92
  project_id: str,
93
93
  task_history_json: Path | None,
94
94
  task_id_list: list[str] | None,
95
- arg_format: FormatArgument,
95
+ arg_format: OutputFormat,
96
96
  temp_dir: Path | None,
97
97
  ):
98
98
  """
@@ -111,7 +111,7 @@ class ListTaskHistoryWithJson(CommandLine):
111
111
  main_obj = ListTaskHistoryWithJsonMain(self.service)
112
112
  task_history_dict = main_obj.get_task_history_dict(project_id, task_history_json=task_history_json, task_id_list=task_id_list, temp_dir=temp_dir)
113
113
  logger.debug(f"{len(task_history_dict)} 件のタスクの履歴情報を出力します。")
114
- if arg_format == FormatArgument.CSV:
114
+ if arg_format == OutputFormat.CSV:
115
115
  all_task_history_list = main_obj.to_all_task_history_list_from_dict(task_history_dict)
116
116
  self.print_according_to_format(all_task_history_list)
117
117
  else:
@@ -127,7 +127,7 @@ class ListTaskHistoryWithJson(CommandLine):
127
127
  args.project_id,
128
128
  task_history_json=args.task_history_json,
129
129
  task_id_list=task_id_list,
130
- arg_format=FormatArgument(args.format),
130
+ arg_format=OutputFormat(args.format),
131
131
  temp_dir=temp_dir,
132
132
  )
133
133
 
@@ -164,8 +164,8 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
164
164
  )
165
165
 
166
166
  argument_parser.add_format(
167
- choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON],
168
- default=FormatArgument.CSV,
167
+ choices=[OutputFormat.CSV, OutputFormat.JSON, OutputFormat.PRETTY_JSON],
168
+ default=OutputFormat.CSV,
169
169
  )
170
170
  argument_parser.add_output()
171
171