アノテーションクラスの領域数上限をSDKから指定できるようにする - #288
Conversation
| payload["order"] = order | ||
| if attributes: | ||
| payload["attributes"] = attributes | ||
| if max_area_count is not _UNSET: |
There was a problem hiding this comment.
他の箇所では0が無制限、Noneがデフォルトという実装になっており、
_UNSET = デフォルト
という実装について理解に非常に時間がかかりました。
大抵の場合は0が無制限を表し、省略時はデフォルト値を表します。
今回の実装については、maxAreaCountのdb側が無制限= nullだからUNSETという新しい概念を追加したという判断で合ってますか?
かなり特殊な実装なので、なぜこのような実装になっているかコメントがほしいです。次に実装する人が混乱します
There was a problem hiding this comment.
ご認識のとおりです。maxAreaCount は無制限が null で、null 自体が API へ送る値になるため None が「未指定」を兼ねられず、省略 / null / 整数の三値になっています。
他の項目の二値の慣習から外れる点はご指摘どおりなので、マーカーの定義側と create / update 双方の使用箇所に、三値であることとそれぞれの意味をコメントで残しました。
There was a problem hiding this comment.
maxAreaCount は無制限が null で、null 自体が API へ送る値になるため None が「未指定」を兼ねられず、省略 / null / 整数の三値になっています。
という点がコメントで確認できませんでした。
この点がないと、次の実装者がdbまで見に行かないと実装意図がわかりません
| ) | ||
|
|
||
|
|
||
| class _Unset: |
There was a problem hiding this comment.
このようなセンチネルはsdkとして公開する都合上、念のためシングルトンにしておいた方がいいですね。copy.deepcopy や pickle を通ると is 判定が壊れて _Unset が payload に載ってしまうので。
シングルトンとしては普通にこんな感じ
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
There was a problem hiding this comment.
シングルトンにしました。deepcopy / pickle を往復しても is 判定が保たれ、payload にマーカーが載らないことをテストで固定しています。
SDKとして公開する都合上、呼び出し側でキーワード引数がdeepcopyやpickleを 経由することがある。素のインスタンスだと同一性比較が壊れ、マーカー自身が リクエストに載ってしまうため。
省略時はNoneという二値の慣習に対して、この項目はnull自体がAPIの受け付ける 値になるため三値になる。慣習との違いが読み手に伝わらずレビューで疑問が 出たため、マーカーの定義側と使用箇所の双方に意図を残す。
| payload["order"] = order | ||
| if attributes: | ||
| payload["attributes"] = attributes | ||
| if max_area_count is not _UNSET: |
There was a problem hiding this comment.
maxAreaCount は無制限が null で、null 自体が API へ送る値になるため None が「未指定」を兼ねられず、省略 / null / 整数の三値になっています。
という点がコメントで確認できませんでした。
この点がないと、次の実装者がdbまで見に行かないと実装意図がわかりません
maxAreaCount は API 側で無制限を null で表すため None が「未指定」を兼ねられず 三値になるという理由を、対象フィールド名と API 仕様を名指しする形で明記した。 従来の英語かつ一般化した説明では、レビューで意図が確認できなかったため。 #288 (comment)
|
@h-iwata san |
| payload["order"] = order | ||
| if attributes: | ||
| payload["attributes"] = attributes | ||
| if max_area_count is not _UNSET: |
SDK 全体のコメントが英語で統一されているため、日本語化した分を戻した。 maxAreaCount を名指しし null が無制限を表すという API 仕様を書く具体度は そのまま英語で維持した。 #288 (comment)
概要
アノテーションクラスの「1アノテーションあたりの領域数上限」(
maxAreaCount)を、SDKから指定・変更できるようにしました。これまでSDKでアノテーションクラスを作成すると、この値がAPI側で常に
1に固定されていました。1だと飛び地のセグメンテーションができないため、SDKでクラスを同期している案件では、作成後に画面から1クラスずつ「なし」に直す必要がありました。更新メソッドでも変更できず、既存クラスをまとめて直す手段もありませんでした。省略時の値は従来どおり
1です(API側の既定値)。既存の連携の振る舞いが変わらないようにするためです。対応内容
create_annotationにmax_area_countを追加。省略時はリクエストに含めず、API側の既定値1に委ねる。Noneを指定すると「なし」(無制限)、整数を指定するとその値update_annotationにmax_area_countを追加。省略時はリクエストに含めないため変更されず、Noneを指定すると「なし」(無制限)作成・更新のどちらも「引数を渡さなかった」と「
Noneを渡した」を区別する必要があります。Noneは「上限なし」という意味でAPIへ送る値なので、「未指定」の意味を兼ねられません。そのためモジュール内にセンチネル_UNSETを置き、それ以外が渡されたときだけmaxAreaCountをリクエストに載せています。センチネルの型注釈はAnyにして、公開されるシグネチャがOptional[int]のままになるようにしています。有効範囲(1〜1000)と整数かどうかの検証はAPI側に委ねています(422が返ります)。同じ
Clientにlimitなどをクライアント側で弾く例はありますが、上限値をリポジトリを跨いで二重に持たないほうがよいと判断しました。テスト
tests/test_annotation.pyを追加。作成・更新それぞれで、省略時にキーを送らないこと、Noneと整数がそのまま載ることを検証clientfixture とリクエスト記録ヘルパーをtests/conftest.pyへ移動。tests/test_workspace_user.pyに同じものが定義されていたため、そちらも共有fixtureを使う形に書き換えています(振る舞いの変更はありません)依存
API側の対応(fastlabel/fastlabel-application#11670)のリリース後に、このPRをマージ・リリースしてください。
max_area_countを指定したときだけAPI側の対応が必要で、指定しない既存の呼び出しはリクエスト内容が従来と同一のため、API側のリリース前でも影響ありません。動作確認
CIはlintとPython 3.10〜3.14のマトリクスが全てpassしています。
あわせて、ローカル環境のAPI(#11670 のブランチのコード)に対してこのSDKから実際にリクエストを投げ、作成(省略時
1/None/ 整数 / 境界値 / 範囲外は422)、更新(省略時は変更なし /None/ 整数 / 範囲外は422で既存値も変わらない)、読み出し、max_area_countを渡さない従来どおりの呼び出しを確認しています。