|
5 | 5 | import json |
6 | 6 | import logging |
7 | 7 | import uuid |
8 | | -from collections.abc import Sequence |
| 8 | +from collections.abc import Mapping, Sequence |
9 | 9 | from dataclasses import dataclass |
10 | 10 | from pathlib import Path |
11 | 11 | from typing import Any |
@@ -49,6 +49,22 @@ class ChoiceAttributeInput: |
49 | 49 | """属性のデフォルト値として使用する選択肢かどうか""" |
50 | 50 |
|
51 | 51 |
|
| 52 | +@dataclass(frozen=True) |
| 53 | +class ResolvedChoiceAttributeInput: |
| 54 | + """ |
| 55 | + 既存アノテーション仕様に対して解決済みの選択肢系属性入力。 |
| 56 | + """ |
| 57 | + |
| 58 | + target_labels: list[Mapping[str, Any]] |
| 59 | + """属性を追加する対象ラベル一覧。""" |
| 60 | + |
| 61 | + new_attribute: dict[str, Any] |
| 62 | + """Annofab API向けの新規属性オブジェクト。""" |
| 63 | + |
| 64 | + duplicated_name_attribute_ids: list[str] |
| 65 | + """同名属性の既存属性ID一覧。""" |
| 66 | + |
| 67 | + |
52 | 68 | def parse_choice_input_from_dict(data: dict[str, Any], *, index: int) -> ChoiceAttributeInput: |
53 | 69 | """ |
54 | 70 | JSONオブジェクト1件を選択肢入力に変換する。 |
@@ -287,6 +303,88 @@ def create_attribute( |
287 | 303 | } |
288 | 304 |
|
289 | 305 |
|
| 306 | +def resolve_choice_attribute_input( |
| 307 | + annotation_specs: dict[str, Any], |
| 308 | + *, |
| 309 | + attribute_type: str, |
| 310 | + attribute_name_en: str, |
| 311 | + attribute_name_ja: str | None, |
| 312 | + attribute_id: str | None, |
| 313 | + choice_inputs: Sequence[ChoiceAttributeInput], |
| 314 | + label_ids: Sequence[str] | None, |
| 315 | + label_name_ens: Sequence[str] | None, |
| 316 | +) -> ResolvedChoiceAttributeInput: |
| 317 | + """ |
| 318 | + 選択肢系属性入力を既存アノテーション仕様に対して解決する。 |
| 319 | +
|
| 320 | + Args: |
| 321 | + annotation_specs: 既存のアノテーション仕様 |
| 322 | + attribute_type: 属性型。 ``choice`` または ``select`` |
| 323 | + attribute_name_en: 属性英語名 |
| 324 | + attribute_name_ja: 属性日本語名 |
| 325 | + attribute_id: 属性ID。未指定ならUUIDv4を自動生成 |
| 326 | + choice_inputs: 選択肢入力一覧 |
| 327 | + label_ids: 追加先ラベルID一覧。未指定時はNone |
| 328 | + label_name_ens: 追加先ラベル英語名一覧。未指定時はNone |
| 329 | +
|
| 330 | + Returns: |
| 331 | + 解決済み選択肢系属性入力 |
| 332 | + """ |
| 333 | + annotation_specs_accessor = AnnotationSpecsAccessor(annotation_specs) |
| 334 | + target_labels = get_target_labels(annotation_specs_accessor, label_ids=label_ids, label_name_ens=label_name_ens) |
| 335 | + new_attribute = create_attribute( |
| 336 | + attribute_type=attribute_type, |
| 337 | + attribute_name_en=attribute_name_en, |
| 338 | + attribute_name_ja=attribute_name_ja, |
| 339 | + attribute_id=attribute_id, |
| 340 | + choice_inputs=choice_inputs, |
| 341 | + ) |
| 342 | + duplicated_name_attribute_ids = validate_new_attribute( |
| 343 | + annotation_specs["additionals"], |
| 344 | + attribute_id=new_attribute["additional_data_definition_id"], |
| 345 | + attribute_name_en=attribute_name_en, |
| 346 | + ) |
| 347 | + return ResolvedChoiceAttributeInput( |
| 348 | + target_labels=target_labels, |
| 349 | + new_attribute=new_attribute, |
| 350 | + duplicated_name_attribute_ids=duplicated_name_attribute_ids, |
| 351 | + ) |
| 352 | + |
| 353 | + |
| 354 | +def build_request_body_for_add_choice_attribute( |
| 355 | + annotation_specs: dict[str, Any], |
| 356 | + *, |
| 357 | + resolved_choice_attribute_input: ResolvedChoiceAttributeInput, |
| 358 | + attribute_name_en: str, |
| 359 | + comment: str | None, |
| 360 | +) -> dict[str, Any]: |
| 361 | + """ |
| 362 | + 選択肢系属性追加用の request body を生成する。 |
| 363 | +
|
| 364 | + Args: |
| 365 | + annotation_specs: 既存のアノテーション仕様 |
| 366 | + resolved_choice_attribute_input: 解決済み選択肢系属性入力 |
| 367 | + attribute_name_en: 属性英語名 |
| 368 | + comment: 変更コメント |
| 369 | +
|
| 370 | + Returns: |
| 371 | + Annofab API に渡す request body |
| 372 | + """ |
| 373 | + request_body = copy.deepcopy(annotation_specs) |
| 374 | + request_body["additionals"].append(resolved_choice_attribute_input.new_attribute) |
| 375 | + target_label_id_set = {label["label_id"] for label in resolved_choice_attribute_input.target_labels} |
| 376 | + for label in request_body["labels"]: |
| 377 | + if label["label_id"] in target_label_id_set: |
| 378 | + label["additional_data_definitions"].append(resolved_choice_attribute_input.new_attribute["additional_data_definition_id"]) |
| 379 | + |
| 380 | + label_names = [get_label_name_en(label) for label in resolved_choice_attribute_input.target_labels] |
| 381 | + if comment is None: |
| 382 | + comment = create_comment_from_attribute(attribute_name_en, label_names) |
| 383 | + request_body["comment"] = comment |
| 384 | + request_body["last_updated_datetime"] = annotation_specs["updated_datetime"] |
| 385 | + return request_body |
| 386 | + |
| 387 | + |
290 | 388 | class AddChoiceAttributeMain(CommandLineWithConfirm): |
291 | 389 | """ |
292 | 390 | 選択肢系属性をアノテーション仕様へ追加する本体処理。 |
@@ -335,44 +433,36 @@ def add_choice_attribute( |
335 | 433 | ValueError: 入力値や既存アノテーション仕様との整合性が不正な場合 |
336 | 434 | """ |
337 | 435 | old_annotation_specs, _ = self.service.api.get_annotation_specs(self.project_id, query_params={"v": "3"}) |
338 | | - annotation_specs_accessor = AnnotationSpecsAccessor(old_annotation_specs) |
339 | | - additionals = old_annotation_specs["additionals"] |
340 | | - target_labels = get_target_labels(annotation_specs_accessor, label_ids=label_ids, label_name_ens=label_name_ens) |
341 | | - new_attribute = create_attribute( |
| 436 | + resolved_choice_attribute_input = resolve_choice_attribute_input( |
| 437 | + old_annotation_specs, |
342 | 438 | attribute_type=attribute_type, |
343 | 439 | attribute_name_en=attribute_name_en, |
344 | 440 | attribute_name_ja=attribute_name_ja, |
345 | 441 | attribute_id=attribute_id, |
346 | 442 | choice_inputs=choice_inputs, |
| 443 | + label_ids=label_ids, |
| 444 | + label_name_ens=label_name_ens, |
347 | 445 | ) |
348 | 446 |
|
349 | | - duplicated_name_attribute_ids = validate_new_attribute( |
350 | | - additionals, |
351 | | - attribute_id=new_attribute["additional_data_definition_id"], |
352 | | - attribute_name_en=attribute_name_en, |
| 447 | + label_names = [get_label_name_en(label) for label in resolved_choice_attribute_input.target_labels] |
| 448 | + confirm_message = ( |
| 449 | + f"属性名(英語)='{attribute_name_en}', 属性種類='{attribute_type}', " |
| 450 | + f"選択肢数={len(resolved_choice_attribute_input.new_attribute['choices'])}, 対象ラベル={label_names} を追加します。よろしいですか?" |
353 | 451 | ) |
354 | | - |
355 | | - label_names = [get_label_name_en(label) for label in target_labels] |
356 | | - confirm_message = f"属性名(英語)='{attribute_name_en}', 属性種類='{attribute_type}', 選択肢数={len(new_attribute['choices'])}, 対象ラベル={label_names} を追加します。よろしいですか?" |
357 | | - if duplicated_name_attribute_ids: |
358 | | - duplicated_text = ", ".join(duplicated_name_attribute_ids) |
| 452 | + if resolved_choice_attribute_input.duplicated_name_attribute_ids: |
| 453 | + duplicated_text = ", ".join(resolved_choice_attribute_input.duplicated_name_attribute_ids) |
359 | 454 | confirm_message = f"{confirm_message} なお、同じ属性名(英語)を持つ既存属性があります。既存の属性ID: {duplicated_text}" |
360 | 455 | if not self.confirm_processing(confirm_message): |
361 | 456 | return False |
362 | 457 |
|
363 | | - request_body = copy.deepcopy(old_annotation_specs) |
364 | | - request_body["additionals"].append(new_attribute) |
365 | | - target_label_id_set = {label["label_id"] for label in target_labels} |
366 | | - for label in request_body["labels"]: |
367 | | - if label["label_id"] in target_label_id_set: |
368 | | - label["additional_data_definitions"].append(new_attribute["additional_data_definition_id"]) |
369 | | - |
370 | | - if comment is None: |
371 | | - comment = create_comment_from_attribute(attribute_name_en, label_names) |
372 | | - request_body["comment"] = comment |
373 | | - request_body["last_updated_datetime"] = old_annotation_specs["updated_datetime"] |
| 458 | + request_body = build_request_body_for_add_choice_attribute( |
| 459 | + old_annotation_specs, |
| 460 | + resolved_choice_attribute_input=resolved_choice_attribute_input, |
| 461 | + attribute_name_en=attribute_name_en, |
| 462 | + comment=comment, |
| 463 | + ) |
374 | 464 | self.service.api.put_annotation_specs(self.project_id, query_params={"v": "3"}, request_body=request_body) |
375 | | - logger.info(f"属性名(英語)='{attribute_name_en}', 属性ID='{new_attribute['additional_data_definition_id']}' の選択肢系属性を追加しました。") |
| 465 | + logger.info(f"属性名(英語)='{attribute_name_en}', 属性ID='{resolved_choice_attribute_input.new_attribute['additional_data_definition_id']}' の選択肢系属性を追加しました。") |
376 | 466 | return True |
377 | 467 |
|
378 | 468 |
|
|
0 commit comments