-
Notifications
You must be signed in to change notification settings - Fork 122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat!: AudioQueryのJSON表現をENGINEと同じにする #946
feat!: AudioQueryのJSON表現をENGINEと同じにする #946
Conversation
b044c07
to
04dadbd
Compare
@pydantic.dataclasses.dataclass( | ||
config=ConfigDict(alias_generator=_rename_audio_query_field), | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なんか幸いなことに、特に設定しない場合の挙動はエイリアスというよりはリネームっぽい。snake_caseなやつを受け付けなくなった。
(ただしこれはdataclassとしてのフィールド名には影響しないので、そっちの対処はこの下の__post_init__
で行う。)
@@ -231,6 +242,19 @@ class AudioQuery: | |||
のAudioQueryでは無視される。 | |||
""" | |||
|
|||
# `dataclasses.asdict`の内部実装に依存したハックだが、他に方法が思い付かなかった。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
よく考えてみれば別にそんなにハックじゃないような気がしてきた。問題はdataclasses.Field.name
を外から書き換えていいのかといったところか。駄目とは書かれていない。
crates/voicevox_core_python_api/python/voicevox_core/_models.py
Outdated
Show resolved
Hide resolved
9a0fe3a
to
31b8dea
Compare
if true_name := self.__attr_true_names.get(name): | ||
return getattr(self, true_name) | ||
# 普通の`AttributeError`と同じ文面 | ||
raise AttributeError(f"{type(self).__name__!r} has no attribute {name!r}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
多分こういう書き方もあるが、どっちが良いかどうか正直わからない。
if true_name := self.__attr_true_names.get(name): | |
return getattr(self, true_name) | |
# 普通の`AttributeError`と同じ文面 | |
raise AttributeError(f"{type(self).__name__!r} has no attribute {name!r}") | |
try: | |
true_name = self.__attr_true_names[name] | |
except KeyError: | |
# 普通の`AttributeError`と同じ文面 | |
raise AttributeError(f"{type(self).__name__!r} has no attribute {name!r}") | |
return getattr(self, true_name) |
def __post_init__(self) -> None: | ||
""" | ||
:func:`dataclasses.asdict` にてキーが正しい名前になるよう、 ``dataclass`` | ||
としてのフィールドをハックする。 | ||
""" | ||
|
||
self.__attr_true_names: dict[str, str] = {} | ||
for field in dataclasses.fields(self): | ||
if (rename := _rename_audio_query_field(field.name)) != field.name: | ||
self.__attr_true_names[rename] = field.name | ||
field.name = rename | ||
|
||
def __getattr__(self, name: str) -> object: | ||
"""camelCaseの名前に対し、対応するsnake_caseの名前があるならそれについて返す。""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
すみません、確認です!
AudioQuery jsonとコード内でのデータ形式どちらともキャメルケースとスネークケースが入り混じる形になるようになった、という感じでしょうか?(違いそう)
そもそもエンジンと同じにすべきなのかを少し迷っています。同じにした方が良い気もしつつ、スネークケース統一だった方が良い気もしつつ………難しいですね。。。
あ、コード内はsnake_caseのまま、JSON表現だけをENGINEと同じにしています。 よく考えたらPRのタイトルはよくありませんね。変えておきます。 |
なるほどです!! あとはこの点かなぁ
うーーーーーん。 エンジンとAudioQueryのデータ構造を合わせるべきか、ということだよなぁ。 |
#943 (comment)でも書いたのですが、ENGINEとCOREを両方使うケースはあるかなと思ってます。両者を比較検討しているときとか。またユーザーが見るのがドキュメントではなくZennやChatGPTに寄るということも起こりうることを考えると、「ENGINEを使う知識」と「COREを使う知識」の違いは、可能な範囲において小さくした方がよいのではないかと思った次第です。 |
個人的にはこれはかなりレアケースかなと思ってます!
こちらは同感です! となると、シリアライズされるものというか、エンジンが出力するjsonが既存である場合は合わせる形が良さそう。 |
crates/voicevox_core_python_api/python/voicevox_core/_models.py
Outdated
Show resolved
Hide resolved
crates/voicevox_core_python_api/python/voicevox_core/_models.py
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!!
2点だけコメントしてます!
crates/voicevox_core_python_api/python/voicevox_core/_models.py
Outdated
Show resolved
Hide resolved
crates/voicevox_core_python_api/python/test/test_audio_query.py
Outdated
Show resolved
Hide resolved
Refs: VOICEVOX#946 (comment) Co-authored-by: Hiroshiba <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSON化(シリアライズ化)するためにdataclass.asdictを使っていますが、ここがユーザーに露出するとdataclass(のasdict)に依存したAPIになってしまうので、数年後に不便になるだろうなと感じました!
たぶん良いのはserialize
メソッドを作るか、あるいはserialize
関数を別で実装するかだろうなと思いました。
たぶんjsonという文字列も出さない方が良さそう。(jsonをサポートしてるわけではなく、僕たちにとって都合のいいのがたまたまjsonだったというスタンス)
けどまあこうするとRust側のAPIと乖離してしまうし、ちょっと考えないといけないこと多いので大変な気もします。
とりあえず今はasdictで実装し、APIとしてはサポートせず(ドキュメントに書かず)、後回しにするとかもありだと思います!
ちょっと調べたのですが、PydanticのdataclassからJSONを吐き出す経路としては # snake_caseのまま吐き出されてしまう!(当然、JSON → dataclass → JSONと変換したら元のJSONから変化してしまう)
_ = TypeAdapter(AudioQuery).dump_json(query).decode()
# `by_alias`(デフォルトは`False`)を`True`にするとちゃんとcamelCaseで出る
_ = TypeAdapter(AudioQuery).dump_json(query, by_alias=True).decode() これを受け、最早 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!!
ちなみに目標とする0.16リリースの機能の中にシリアライズは含まれていないので、本線進めた方が良いのではとかちょっと思いました・・・!!
あとこれはただの共有ですが、今のバージョンでシリアライズしたものが読み込めることを今後ずっと確認するテストもあると嬉しいかもですね!
まあデータ構造は変わったタイミングで良いはず・・・!
とりあえずapprove頂いてたのでマージ! |
内容
#261 が行われた理由は次の2点である。
pydantic.dataclasses
の対応ができない (PyO3を使ってクレートからwhlを作る #239 (comment))1.は中止、2.もこの度
ダーティハックであるがなんとかする方法を発見したため、pydantic.TypeAdapter
を利用および推奨すればよいことがわかったため、 #261 の取り消しを行う。これでENGINEとCOREでAudioQueryを使い回せるようになる。BREAKING-CHANGE: AudioQueryのJSON表現がENGINEと同じになる。
See-also: https://docs.python.org/3.13/library/dataclasses.html#dataclasses.FieldSee-also: https://docs.pydantic.dev/2.10/api/type_adapter/
関連 Issue
その他