Source code for annofabapi.util.annotation_specs
from collections.abc import Mapping
from typing import Any, Literal, TypedDict
import more_itertools
from annofabapi.models import Lang
from annofabapi.pydantic_models.additional_data_definition_type import AdditionalDataDefinitionType
[docs]
class InternationalizationMessageItem(TypedDict):
"""多言語メッセージの1要素です。"""
lang: str
message: str
[docs]
class InternationalizationMessage(TypedDict):
"""多言語メッセージです。"""
messages: list[InternationalizationMessageItem]
[docs]
class AttributeChoice(TypedDict):
"""属性の選択肢です。"""
choice_id: str
name: InternationalizationMessage
[docs]
class AttributeDefinition(TypedDict):
"""アノテーション仕様上の属性定義です。"""
additional_data_definition_id: str
name: InternationalizationMessage
type: AdditionalDataDefinitionType
choices: list[AttributeChoice] | None
[docs]
class LabelDefinition(TypedDict):
"""アノテーション仕様上のラベル定義です。"""
label_id: str
label_name: InternationalizationMessage
additional_data_definitions: list[str]
[docs]
class LabelNameHolder(TypedDict):
"""ラベル名を持つ情報です。"""
label_name: InternationalizationMessage
[docs]
class NameHolder(TypedDict):
"""多言語化された名前を持つ情報です。"""
name: InternationalizationMessage
[docs]
def get_english_message(internationalization_message: Mapping[str, Any]) -> str:
"""
`InternationalizationMessage`クラスの値から、英語メッセージを取得します。
英語メッセージが見つからない場合は ``ValueError`` をスローします。
Notes:
英語メッセージは必ず存在するはずなので、英語メッセージが見つからない場合は ``ValueError`` をスローするようにしました。
Args:
internationalization_message: 多言語化されたメッセージ。キー ``messages`` が存在している必要があります。
Returns:
指定した言語に対応するメッセージ。
Raises:
ValueError: 英語メッセージが見つからない場合
"""
messages = internationalization_message["messages"]
result = more_itertools.first_true(messages, pred=lambda e: e["lang"] == Lang.EN_US.value)
if result is not None:
return result["message"]
else:
raise ValueError(f"'{internationalization_message}'に英語のメッセージは存在しません。")
STR_LANG = Literal["en-US", "ja-JP", "vi-VN"]
"""
対応している ``lang`` の文字列
"""
[docs]
def get_message_with_lang(internationalization_message: InternationalizationMessage, lang: Lang | STR_LANG) -> str | None:
"""
`InternationalizationMessage`クラスの値から、指定した ``lang`` に対応するメッセージを取得します。
Args:
internationalization_message: 多言語化されたメッセージ。キー ``messages`` が存在している必要があります。
lang: 取得したいメッセージに対応する言語コード。
Returns:
指定した言語に対応するメッセージ。見つからない場合はNoneを返します。
"""
messages = internationalization_message["messages"]
if isinstance(lang, Lang):
str_lang = lang.value
else:
str_lang = str(lang)
result = more_itertools.first_true(messages, pred=lambda e: e["lang"] == str_lang)
if result is not None:
return result["message"]
return None
[docs]
def get_label_name_en(label: Mapping[str, Any]) -> str:
"""
ラベル情報から英語名を取得します。
Args:
label: ラベル情報。キー ``label_name`` が存在している必要があります。
Returns:
ラベルの英語名。
Raises:
ValueError: 英語メッセージが見つからない場合
"""
return get_english_message(label["label_name"])
[docs]
def get_attribute_name_en(attribute: Mapping[str, Any]) -> str:
"""
属性情報から英語名を取得します。
Args:
attribute: 属性情報。キー ``name`` が存在している必要があります。
Returns:
属性の英語名。
Raises:
ValueError: 英語メッセージが見つからない場合
"""
return get_english_message(attribute["name"])
[docs]
def get_choice_name_en(choice: Mapping[str, Any]) -> str:
"""
選択肢情報から英語名を取得します。
Args:
choice: 選択肢情報。キー ``name`` が存在している必要があります。
Returns:
選択肢の英語名。
Raises:
ValueError: 英語メッセージが見つからない場合
"""
return get_english_message(choice["name"])
[docs]
def get_choice(choices: list[AttributeChoice], *, choice_id: str | None = None, choice_name: str | None = None) -> AttributeChoice:
"""
選択肢情報を取得します。
Args:
choice_id: 選択肢ID
choice_name: 選択肢名(英語)
Raises:
ValueError: 'choice_id'か'choice_name'の指定方法が間違っている。または引数に合致する選択肢情報が見つからない。または複数見つかった。
"""
if choice_id is not None and choice_name is not None:
raise ValueError("'choice_id'か'choice_name'のどちらかはNoneにしてください。")
if choice_id is not None:
result = [e for e in choices if e["choice_id"] == choice_id]
elif choice_name is not None:
result = [e for e in choices if get_choice_name_en(e) == choice_name]
else:
raise ValueError("'choice_id'か'choice_name'のどちらかはNone以外にしてください。")
if len(result) == 0:
raise ValueError(f"選択肢情報が見つかりませんでした。 :: choice_id='{choice_id}', choice_name='{choice_name}'")
if len(result) > 1:
raise ValueError(f"選択肢情報が複数({len(result)}件)見つかりました。 :: choice_id='{choice_id}', choice_name='{choice_name}'")
return result[0]
[docs]
def get_attribute(
additionals: list[AttributeDefinition],
*,
attribute_id: str | None = None,
attribute_name: str | None = None,
label: LabelDefinition | None = None,
) -> AttributeDefinition:
"""
属性情報を取得します。
Args:
attribute_id: 属性ID
attribute_name: 属性名(英語)
label: Noneでなければ、指定したラベルに紐づく属性情報を取得します。
Raises:
ValueError: 'attribute_id'か'attribute_name'の指定方法が間違っている。または引数に合致する属性情報が見つからない。または複数見つかった。
"""
if attribute_id is not None and attribute_name is not None:
raise ValueError("'attribute_id'か'attribute_name'のどちらかはNoneにしてください。")
if attribute_id is not None:
result = [e for e in additionals if e["additional_data_definition_id"] == attribute_id]
elif attribute_name is not None:
result = [e for e in additionals if get_attribute_name_en(e) == attribute_name]
else:
raise ValueError("'attribute_id'か'attribute_name'のどちらかはNone以外にしてください。")
label_name = None
if label is not None:
result = [e for e in result if e["additional_data_definition_id"] in label["additional_data_definitions"]]
label_name = get_label_name_en(label)
if len(result) == 0:
raise ValueError(
f"属性情報が見つかりませんでした。 :: attribute_id='{attribute_id}', attribute_name='{attribute_name}', label_name='{label_name}'"
)
if len(result) > 1:
raise ValueError(
f"属性情報が複数({len(result)}件)見つかりました。 :: attribute_id='{attribute_id}', attribute_name='{attribute_name}', label_name='{label_name}'" # noqa: E501
)
return result[0]
[docs]
def get_label(labels: list[LabelDefinition], *, label_id: str | None = None, label_name: str | None = None) -> LabelDefinition:
"""
ラベル情報を取得します。
Args:
label_id: ラベルID
label_name: ラベル名(英語)
Raises:
ValueError: 'label_id'か'label_name'の指定方法が間違っている。または引数に合致するラベル情報が見つからない。または複数見つかった。
"""
if label_id is not None and label_name is not None:
raise ValueError("'label_id'か'label_name'のどちらかはNoneにしてください。")
if label_id is not None:
result = [e for e in labels if e["label_id"] == label_id]
elif label_name is not None:
result = [e for e in labels if get_label_name_en(e) == label_name]
else:
raise ValueError("'label_id'か'label_name'のどちらかはNone以外にしてください。")
if len(result) == 0:
raise ValueError(f"ラベル情報が見つかりませんでした。 :: label_id='{label_id}', label_name='{label_name}'")
if len(result) > 1:
raise ValueError(f"ラベル情報が複数({len(result)}件)見つかりました。 :: label_id='{label_id}', label_name='{label_name}'")
return result[0]
[docs]
class AnnotationSpecsAccessor:
"""
アノテーション仕様の情報にアクセスするためのクラス。
Args:
annotation_specs: アノテーション仕様(v3)の情報
"""
def __init__(self, annotation_specs: dict[str, Any]) -> None:
self.annotation_specs = annotation_specs
self.labels: list[LabelDefinition] = annotation_specs["labels"]
self.additionals: list[AttributeDefinition] = annotation_specs["additionals"]
[docs]
def get_attribute(
self, *, attribute_id: str | None = None, attribute_name: str | None = None, label: LabelDefinition | None = None
) -> AttributeDefinition:
"""
属性情報を取得します。
Args:
attribute_id: 属性ID
attribute_name: 属性名(英語)
label: Noneでなければ、指定したラベルに紐づく属性情報を取得します。
Raises:
ValueError: 'attribute_id'か'attribute_name'の指定方法が間違っている。または引数に合致する属性情報が見つからない。または複数見つかった。
"""
return get_attribute(self.additionals, attribute_id=attribute_id, attribute_name=attribute_name, label=label)
[docs]
def get_label(self, *, label_id: str | None = None, label_name: str | None = None) -> LabelDefinition:
"""
ラベル情報を取得します。
Args:
label_id: ラベルID
label_name: ラベル名(英語)
Raises:
ValueError: 'label_id'か'label_name'の指定方法が間違っている。または引数に合致するラベル情報が見つからない。または複数見つかった。
"""
return get_label(self.labels, label_id=label_id, label_name=label_name)