Skip to content
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

整理: エンジンマニフェストをAPI向けと内部向けで分離 #1359

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build_util/make_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def generate_api_docs_html(schema: str) -> str:
engine_manifest = load_manifest(engine_manifest_path())
library_manager = LibraryManager(
get_save_dir() / "installed_libraries",
engine_manifest.supported_vvlib_manifest_version,
None,
engine_manifest.brand_name,
engine_manifest.name,
engine_manifest.uuid,
Expand Down
2 changes: 1 addition & 1 deletion run.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ def main() -> None:

library_manager = LibraryManager(
get_save_dir() / "installed_libraries",
engine_manifest.supported_vvlib_manifest_version,
None,
engine_manifest.brand_name,
engine_manifest.name,
engine_manifest.uuid,
Expand Down
2 changes: 1 addition & 1 deletion test/benchmark/engine_preparation.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def _generate_engine_fake_server(root_dir: Path) -> TestClient:
engine_manifest = load_manifest(engine_manifest_path())
library_manager = LibraryManager(
get_save_dir() / "installed_libraries",
engine_manifest.supported_vvlib_manifest_version,
None,
engine_manifest.brand_name,
engine_manifest.name,
engine_manifest.uuid,
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def app_params(tmp_path: Path) -> dict[str, Any]:
engine_manifest = load_manifest(engine_manifest_path())
library_manager = LibraryManager(
get_save_dir() / "installed_libraries",
engine_manifest.supported_vvlib_manifest_version,
None,
engine_manifest.brand_name,
engine_manifest.name,
engine_manifest.uuid,
Expand Down
Empty file added test/unit/__init__.py
Empty file.
18 changes: 18 additions & 0 deletions test/unit/test_engine_manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""エンジンマニフェストのテスト"""

from pathlib import Path

from voicevox_engine.engine_manifest import ManifestWrapper


def test_ManifestWrapper_init() -> None:
tarepan marked this conversation as resolved.
Show resolved Hide resolved
"""`ManifestWrapper.from_file()` でインスタンスが生成される。"""
ManifestWrapper.from_file(Path("engine_manifest.json"))
assert True


def test_ManifestWrapper_relative_path() -> None:
"""`ManifestWrapper.root` を用いて相対パスが解決できる。"""
wrapper = ManifestWrapper.from_file(Path("engine_manifest.json"))
tos_path = wrapper.root / wrapper.terms_of_service
assert tos_path.exists()
8 changes: 4 additions & 4 deletions voicevox_engine/app/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from voicevox_engine.app.routers.user_dict import generate_user_dict_router
from voicevox_engine.cancellable_engine import CancellableEngine
from voicevox_engine.core.core_initializer import CoreManager
from voicevox_engine.engine_manifest import EngineManifest
from voicevox_engine.engine_manifest import ManifestWrapper
from voicevox_engine.library.library_manager import LibraryManager
from voicevox_engine.metas.MetasStore import MetasStore
from voicevox_engine.preset.preset_manager import PresetManager
Expand All @@ -37,7 +37,7 @@ def generate_app(
setting_loader: SettingHandler,
preset_manager: PresetManager,
user_dict: UserDictionary,
engine_manifest: EngineManifest,
engine_manifest: ManifestWrapper,
library_manager: LibraryManager,
cancellable_engine: CancellableEngine | None = None,
speaker_info_dir: Path | None = None,
Expand Down Expand Up @@ -73,7 +73,7 @@ def generate_app(
app.include_router(
generate_speaker_router(core_manager, metas_store, speaker_info_dir)
)
if engine_manifest.supported_features.manage_library:
if engine_manifest.supported_features.manage_library.value:
app.include_router(generate_library_router(library_manager))
app.include_router(generate_user_dict_router(user_dict))
app.include_router(generate_engine_info_router(core_manager, engine_manifest))
Expand All @@ -83,7 +83,7 @@ def generate_app(
app.include_router(generate_portal_page_router(engine_manifest.name))

app = configure_openapi_schema(
app, engine_manifest.supported_features.manage_library
app, engine_manifest.supported_features.manage_library.value
)

return app
10 changes: 7 additions & 3 deletions voicevox_engine/app/routers/engine_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
from voicevox_engine import __version__
from voicevox_engine.core.core_adapter import DeviceSupport
from voicevox_engine.core.core_initializer import CoreManager
from voicevox_engine.engine_manifest import EngineManifest
from voicevox_engine.engine_manifest import (
EngineManifest,
ManifestWrapper,
generate_engine_manifest,
)


class SupportedDevicesInfo(BaseModel):
Expand All @@ -32,7 +36,7 @@ def generate_from(cls, device_support: DeviceSupport) -> Self:


def generate_engine_info_router(
core_manager: CoreManager, engine_manifest_data: EngineManifest
core_manager: CoreManager, engine_manifest_data: ManifestWrapper
) -> APIRouter:
"""エンジン情報 API Router を生成する"""
router = APIRouter(tags=["その他"])
Expand Down Expand Up @@ -60,6 +64,6 @@ def supported_devices(
@router.get("/engine_manifest")
async def engine_manifest() -> EngineManifest:
"""エンジンマニフェストを取得します。"""
return engine_manifest_data
return generate_engine_manifest(engine_manifest_data)

return router
27 changes: 21 additions & 6 deletions voicevox_engine/engine_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import json
from base64 import b64encode
from pathlib import Path
from typing import TypeAlias
from typing import Self, TypeAlias

from pydantic import BaseModel, Field
from pydantic.json_schema import SkipJsonSchema
Expand Down Expand Up @@ -57,6 +57,18 @@ class EngineManifestJson(BaseModel):
supported_features: SupportedFeaturesJson


class ManifestWrapper(EngineManifestJson):
# エンジンマニフェストの親ディレクトリのパス。マニフェストの `.icon` 等はここをルートとする相対パスで記述されている。
root: Path

@classmethod
def from_file(cls, manifest_path: Path) -> Self:
"""指定ファイルからインスタンスを生成する。"""
manifest = EngineManifestJson.model_validate_json(manifest_path.read_bytes())
manifest_root = manifest_path.parent
return cls(root=manifest_root, **manifest.model_dump())


class UpdateInfo(BaseModel):
"""
エンジンのアップデート情報
Expand Down Expand Up @@ -131,13 +143,16 @@ class EngineManifest(BaseModel):
supported_features: SupportedFeatures = Field(title="エンジンが持つ機能")


def load_manifest(manifest_path: Path) -> EngineManifest:
def load_manifest(manifest_path: Path) -> ManifestWrapper:
"""エンジンマニフェストを指定ファイルから読み込む。"""
return ManifestWrapper.from_file(manifest_path)


def generate_engine_manifest(manifest_wrapper: ManifestWrapper) -> EngineManifest:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この関数だけ見ると何をやっているのかかなりややこしいですね・・・
ManifestWrapperが何用なのか、wrapperからwrapperじゃないのを作るのがなぜgenerateなのか、だいぶ混乱しそうです。

ちょっと解決策は思いついてないのですが、どういう名付けをしていくのか考える良い機会な気がしました!
このファイルはAPI用なのか内部用なのかも混乱ポイントかもです。他のファイルに揃える形にすると良さそうかも(揃ってるかもですが。。状況がわかっておらず。。)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この関数だけ見ると何をやっているのかかなりややこしい

👍️
妥当な指摘です。
まあ、wrapperとかadapterとかでも良さそう。 とのレビューコメントに基づきこの命名にしましたが、たしかにパッと見、よくわからないですね。

このあたり、本質的にややこしいのが厄介です。以下の3種類のクラスが必ず必要になります:

  • A. マニフェストファイル JSON をそのまま Python 化したもの(EngineManifestJson
  • B. マニフェストの情報を利用可能に整理したもの(ManifestWrapper
  • C. マニフェストの情報を API 返り値用に一部削除・データ読み込みしたもの(EngineManifest

どれもマニフェストの実体に関連するクラスなので、さてどう命名したもんか、という感じです。
B→C は cast ではあるけどファイル読み込みもしてるし、generate_api_manifest()別物かと思っちゃう のと指摘を頂いているし…。
良い案があれば提案いただきたいです🙇

どういう名付けをしていくのか考える良い機会

👍️
同意です。
ref #1385 (comment)

このファイルはAPI用なのか内部用なのかも混乱ポイントかもです。

👍️
妥当な指摘です。
本 PR merge 後に router へ移動する PR を出す予定です。これにより API / router 用クラス(EngineManifest)と内部用クラス(ManifestWrapper)がモジュール単位で分離します。

Copy link
Member

@Hiroshiba Hiroshiba Jun 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

まあ、wrapperとかadapterとかでも良さそう。 とのレビューコメントに基づきこの命名にしましたが、たしかにパッと見、よくわからないですね。

すみません、これが内部用のデータ構造だと思ってなくてそうコメントしてました。。


良い名付けは思いつかないです。。ちょっと考えるの任せたいです…😇
良い前例に倣うのが手っ取り早い気はしています。どこに前例があるのか知らないのですが…。

とりあえず前例を調べまくってましたが、データ変換はDTOと呼ぶくらいしか出てきませんでした。
そもそもこのファイルは何の概念の中にあるのか、Api側は何の層の概念なのかとかを整理して名付けするとか…?
頭文字だけつける…?Model…?Inner…?
みたいな感じです…😇

Copy link
Member

@Hiroshiba Hiroshiba Jun 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本 PR merge 後に router へ移動する PR を出す予定です。これにより API / router 用クラス(EngineManifest)と内部用クラス(ManifestWrapper)がモジュール単位で分離します。

ちなみに、そこまで実装したものをPRにするとどれくらいの変更量になりそうでしょうか。
プラマイ合計150くらいならそこそこコンパクトな気がしてます。

というのも、「途中なので一旦これで」となってるのは、実は最小機能よりも細かい変更をしていて、プルリクを小さく分けすぎてるかも?と思ったためです。
@tarepan さんにとって名付けに違和感少ないのであれば、もしかしたら完成系が浮かんでるからで、完成系ならこの名付け問題も起こってないかも、みたいな…。
ちょっと試しになのですが、この辺り完成まで一気に変更するとどうなるかPRお願いしても良いでしょうか……🙇

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

考えるの任せたい

👍️
承知しました。

命名はとても大事である一方、思い付かないときは思い付かないし最悪変な名前でも動く、との認識です。
author/reviewer 双方が名案を思い付かない場合、FIXME をつけつつ妥協案の名前を採用するのがベターだと考えます。良い変数名が思い付かないから機能追加/リファクタリングを merge しない、はプロジェクト全体の進行を考えるとアンバランスなケースが多そうです。

試しになのですが、この辺り完成まで一気に変更するとどうなるかPRお願いしても良いでしょうか

👍️
本 PR 内で移動含めてリファクタリングします。


プルリクを小さく分けすぎてるかも

以前のレビューで @Hiroshiba さんから「コード移動とコード変更を同時にしないでほしい」との旨の指摘を頂きました(無変更の純粋なコード移動であればレビューツールで自動検知できる的な話だったような)。
それ以降はこれに従い、サイズ関わらず移動と変更を別 PR に分割する方針を取っています。

「同時に移動/変更するのは OK」へ方針変更した、という認識で良いでしょうか?あるいは「今回は命名が難しいので全体像優先で同時に移動/変更」という感じでしょうか?

Copy link
Member

@Hiroshiba Hiroshiba Jun 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

author/reviewer 双方が名案を思い付かない場合、FIXME をつけつつ妥協案の名前を採用するのがベターだと考えます。良い変数名が思い付かないから機能追加/リファクタリングを merge しない、はプロジェクト全体の進行を考えるとアンバランスなケースが多そうです。

その時は僕がなんとかして考えます!
極端な話、関数名・変数名がぜんぶA B C D EみたいになってるPRは弾くと思います。
ちゃんと考えれてないですが、納得する言い方としては「機能が揃ってからmergeがよく、名前は機能の1つ」とかなのかなと。

Copy link
Member

@Hiroshiba Hiroshiba Jun 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

以前のレビューで @Hiroshiba さんから「コード移動とコード変更を同時にしないでほしい」との旨の指摘を頂きました(無変更の純粋なコード移動であればレビューツールで自動検知できる的な話だったような)。
それ以降はこれに従い、サイズ関わらず移動と変更を別 PR に分割する方針を取っています。

「同時に移動/変更するのは OK」へ方針変更した、という認識で良いでしょうか?あるいは「今回は命名が難しいので全体像優先で同時に移動/変更」という感じでしょうか?

なるほどです!!
そういう意図だということに気づいていませんでした。

実装→移動 ではなく 移動→リファクタリング にするとどうでしょう・・・?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

その時は僕がなんとかして考えます!

👍️
困った場合はお任せします。


実装→移動 ではなく 移動→リファクタリング

👍️
可能なケースではその順序で PR 提出する方針に変更します。

"""API 向けのエンジンマニフェストを生成する。"""
root_dir = manifest_wrapper.root
manifest = manifest_wrapper.model_dump()

root_dir = manifest_path.parent
manifest = EngineManifestJson.model_validate_json(
manifest_path.read_bytes()
).model_dump()
return EngineManifest(
manifest_version=manifest["manifest_version"],
name=manifest["name"],
Expand Down