Skip to content

Commit

Permalink
annotation_specs exportコマンドの作成 (#1178)
Browse files Browse the repository at this point in the history
* print_according_to_format関数の引数arg_formatをformatに変更

* コマンド追加

* add document

* add test

* pylintrc
  • Loading branch information
yuji38kwmt authored Mar 21, 2024
1 parent addf12b commit 1c9fe17
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 15 deletions.
113 changes: 113 additions & 0 deletions annofabcli/annotation_specs/export_annotation_specs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from __future__ import annotations

import argparse
import logging
import sys
from typing import Optional

import annofabcli
import annofabcli.common.cli
from annofabcli.common.cli import (
COMMAND_LINE_ERROR_STATUS_CODE,
AbstractCommandLineInterface,
ArgumentParser,
build_annofabapi_resource_and_login,
)
from annofabcli.common.enums import FormatArgument
from annofabcli.common.facade import AnnofabApiFacade
from annofabcli.common.utils import print_according_to_format

logger = logging.getLogger(__name__)


class ListAttributeRestriction(AbstractCommandLineInterface):
COMMON_MESSAGE = "annofabcli annotation_specs export: error:"

def get_history_id_from_before_index(self, project_id: str, before: int) -> Optional[str]:
histories, _ = self.service.api.get_annotation_specs_histories(project_id)
if before + 1 > len(histories):
logger.warning(f"アノテーション仕様の履歴は{len(histories)}個のため、最新より{before}個前のアノテーション仕様は見つかりませんでした。")
return None
history = histories[-(before + 1)]
return history["history_id"]

def main(self) -> None:
args = self.args

history_id = None
if args.history_id is not None:
history_id = args.history_id

if args.before is not None:
history_id = self.get_history_id_from_before_index(args.project_id, args.before)
if history_id is None:
print(
f"{self.COMMON_MESSAGE} argument --before: 最新より{args.before}個前のアノテーション仕様は見つかりませんでした。",
file=sys.stderr,
)
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)

query_params = {"v": "3"}
if history_id is not None:
query_params["history_id"] = history_id

annotation_specs, _ = self.service.api.get_annotation_specs(args.project_id, query_params=query_params)

print_according_to_format(annotation_specs, format=FormatArgument(args.format), output=args.output)


def parse_args(parser: argparse.ArgumentParser) -> None:
argument_parser = ArgumentParser(parser)

argument_parser.add_project_id()

# 過去のアノテーション仕様を参照するためのオプション
old_annotation_specs_group = parser.add_mutually_exclusive_group()
old_annotation_specs_group.add_argument(
"--history_id",
type=str,
help=(
"出力したいアノテーション仕様のhistory_idを指定してください。 "
"history_idは ``annotation_specs list_history`` コマンドで確認できます。 "
"指定しない場合は、最新のアノテーション仕様が出力されます。 "
),
)

old_annotation_specs_group.add_argument(
"--before",
type=int,
help=(
"出力したい過去のアノテーション仕様が、最新よりいくつ前のアノテーション仕様であるかを指定してください。 "
"たとえば ``1`` を指定した場合、最新より1個前のアノテーション仕様を出力します。 "
"指定しない場合は、最新のアノテーション仕様が出力されます。 "
),
)

argument_parser.add_output()

parser.add_argument(
"-f",
"--format",
type=str,
choices=[FormatArgument.JSON.value, FormatArgument.PRETTY_JSON.value],
default=FormatArgument.JSON.value,
help="出力フォーマット",
)

parser.set_defaults(subcommand_func=main)


def main(args: argparse.Namespace) -> None:
service = build_annofabapi_resource_and_login(args)
facade = AnnofabApiFacade(service)
ListAttributeRestriction(service, facade, args).main()


def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
subcommand_name = "export"

subcommand_help = "アノテーション仕様の情報をエクスポートします。"

parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help)
parse_args(parser)
return parser
2 changes: 1 addition & 1 deletion annofabcli/annotation_specs/list_annotation_specs_label.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def print_annotation_specs_label(self, project_id: str, arg_format: str, output:
self._print_text_format_labels(labels_v1, output=output)

elif arg_format in [FormatArgument.JSON.value, FormatArgument.PRETTY_JSON.value]:
annofabcli.common.utils.print_according_to_format(target=labels_v1, arg_format=FormatArgument(arg_format), output=output)
annofabcli.common.utils.print_according_to_format(target=labels_v1, format=FormatArgument(arg_format), output=output)

@staticmethod
def _get_name_tuple(messages: List[Dict[str, Any]]) -> tuple[str, str]:
Expand Down
2 changes: 2 additions & 0 deletions annofabcli/annotation_specs/subcommand_annotation_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Optional

import annofabcli
import annofabcli.annotation_specs.export_annotation_specs
import annofabcli.annotation_specs.get_annotation_specs_with_attribute_id_replaced
import annofabcli.annotation_specs.get_annotation_specs_with_choice_id_replaced
import annofabcli.annotation_specs.get_annotation_specs_with_label_id_replaced
Expand All @@ -17,6 +18,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
subparsers = parser.add_subparsers(dest="subcommand_name")

# サブコマンドの定義
annofabcli.annotation_specs.export_annotation_specs.add_parser(subparsers)
annofabcli.annotation_specs.get_annotation_specs_with_attribute_id_replaced.add_parser(subparsers)
annofabcli.annotation_specs.get_annotation_specs_with_choice_id_replaced.add_parser(subparsers)
annofabcli.annotation_specs.get_annotation_specs_with_label_id_replaced.add_parser(subparsers)
Expand Down
2 changes: 1 addition & 1 deletion annofabcli/common/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ def print_csv(self, df: pandas.DataFrame) -> None:
def print_according_to_format(self, target: Any) -> None: # noqa: ANN401
target = self.search_with_jmespath_expression(target)

print_according_to_format(target, arg_format=FormatArgument(self.str_format), output=self.output, csv_format=self.csv_format)
print_according_to_format(target, format=FormatArgument(self.str_format), output=self.output, csv_format=self.csv_format)


class PrettyHelpFormatter(argparse.RawTextHelpFormatter, argparse.ArgumentDefaultsHelpFormatter):
Expand Down
18 changes: 9 additions & 9 deletions annofabcli/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def print_id_list(id_list: List[Any], output: Optional[Union[str, Path]]) -> Non

def print_according_to_format( # noqa: ANN201
target: Any, # noqa: ANN401
arg_format: FormatArgument,
format: FormatArgument, # noqa: A002
output: Optional[Union[str, Path]] = None,
csv_format: Optional[Dict[str, Any]] = None,
):
Expand All @@ -124,33 +124,33 @@ def print_according_to_format( # noqa: ANN201
"""

if arg_format == FormatArgument.PRETTY_JSON:
if format == FormatArgument.PRETTY_JSON:
print_json(target, is_pretty=True, output=output)

elif arg_format == FormatArgument.JSON:
elif format == FormatArgument.JSON:
print_json(target, is_pretty=False, output=output)

elif arg_format == FormatArgument.CSV:
elif format == FormatArgument.CSV:
df = pandas.DataFrame(target)
print_csv(df, output=output, to_csv_kwargs=csv_format)

elif arg_format == FormatArgument.TASK_ID_LIST:
elif format == FormatArgument.TASK_ID_LIST:
task_id_list = [e["task_id"] for e in target]
print_id_list(task_id_list, output)

elif arg_format == FormatArgument.PROJECT_ID_LIST:
elif format == FormatArgument.PROJECT_ID_LIST:
project_id_list = [e["project_id"] for e in target]
print_id_list(project_id_list, output)

elif arg_format == FormatArgument.INPUT_DATA_ID_LIST:
elif format == FormatArgument.INPUT_DATA_ID_LIST:
input_data_id_list = [e["input_data_id"] for e in target]
print_id_list(input_data_id_list, output)

elif arg_format == FormatArgument.USER_ID_LIST:
elif format == FormatArgument.USER_ID_LIST:
user_id_list = [e["user_id"] for e in target]
print_id_list(user_id_list, output)

elif arg_format == FormatArgument.INSPECTION_ID_LIST:
elif format == FormatArgument.INSPECTION_ID_LIST:
inspection_id_list = [e["inspection_id"] for e in target]
print_id_list(inspection_id_list, output)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def print_summarize_task_count(self, df: pandas.DataFrame) -> None:
columns = ["task_id_group"] + [status.value for status in TaskStatusForSummary] + ["sum"]
annofabcli.common.utils.print_according_to_format(
df[columns],
arg_format=FormatArgument(FormatArgument.CSV),
format=FormatArgument(FormatArgument.CSV),
output=self.output,
csv_format=self.csv_format,
)
Expand Down
2 changes: 1 addition & 1 deletion annofabcli/statistics/summarize_task_count_by_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def print_summarize_df(self, df: pandas.DataFrame) -> None:
target_df = df[columns].sort_values("user_id")
annofabcli.common.utils.print_according_to_format(
target_df,
arg_format=FormatArgument(FormatArgument.CSV),
format=FormatArgument(FormatArgument.CSV),
output=self.output,
csv_format=self.csv_format,
)
Expand Down
57 changes: 57 additions & 0 deletions docs/command_reference/annotation_specs/export.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
====================================================================================
annotation_specs export
====================================================================================

Description
=================================
アノテーション仕様の情報をJSON形式でエクスポートします。
このJSONは、`アノテーション仕様のインポート機能 <https://annofab.readme.io/docs/annotation-specs#%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88>`_ で利用できます。


Examples
=================================

基本的な使い方
--------------------------


.. code-block::
$ annofabcli annotation_specs export --project_id prj1 --out out.json --format pretty_json
.. code-block::
:caption: out.json
{
"labels": [
"label_id": "763e1659-94c8-4424-9fc8-11b8fbcb115f",
"label_name": {...},
...
],
"additionals": [...],
...
}
.. warning::

``annotation_specs export`` コマンドの出力結果は、アノテーション仕様のインポート機能で利用することを目的として作られています。

JSON構造は、将来変更される可能性があります。
``annotation_specs export`` コマンドの出力結果であるJSONの構造に直接依存したプログラムを作成する場合は、ご注意ください。




Usage Details
=================================

.. argparse::
:ref: annofabcli.annotation_specs.export_annotation_specs.add_parser
:prog: annofabcli annotation_specs export
:nosubcommands:
:nodefaultconst:

1 change: 1 addition & 0 deletions docs/command_reference/annotation_specs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Available Commands
:maxdepth: 1
:titlesonly:

export
get_with_attribute_id_replaced_label_name
get_with_choice_id_replaced_label_name
get_with_label_id_replaced_label_name
Expand Down
5 changes: 3 additions & 2 deletions pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ disable=
no-else-return,
f-string-without-interpolation,
broad-except,

unused-argument, # ruffの"ARG002"でチェックしているため

unused-argument, # ruffの"ARG002"でチェックしているため
redefined-builtin, # ruffの"builtin-argument-shadowin"でチェックしているため無視する
13 changes: 13 additions & 0 deletions tests/test_annotation_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@
class TestCommandLine:
command_name = "annotation_specs"

def test__export_annotation_specs(self):
out_file = str(out_dir / "annotation_specs__export.json")
main(
[
self.command_name,
"export",
"--project_id",
project_id,
"--output",
out_file,
]
)

def test_annotation_specs_list_restriction(self):
out_file = str(out_dir / "annotation_specs_list_restriction.txt")
main(
Expand Down

0 comments on commit 1c9fe17

Please sign in to comment.