Skip to content

Commit

Permalink
Python APIとJava APIのエラーを詳細にする
Browse files Browse the repository at this point in the history
  • Loading branch information
qryxip committed Oct 12, 2023
1 parent 43b63d7 commit 97e711a
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import conftest
import pytest
import pytest_asyncio
from voicevox_core import OpenJtalk, Synthesizer, VoicevoxError
from voicevox_core import OpenJtalk, Synthesizer


def test_enter_returns_workable_self(synthesizer: Synthesizer) -> None:
Expand All @@ -24,14 +24,14 @@ def test_closing_multiple_times_is_allowed(synthesizer: Synthesizer) -> None:

def test_access_after_close_denied(synthesizer: Synthesizer) -> None:
synthesizer.close()
with pytest.raises(VoicevoxError, match="^The `Synthesizer` is closed$"):
with pytest.raises(ValueError, match="^The `Synthesizer` is closed$"):
_ = synthesizer.metas


def test_access_after_exit_denied(synthesizer: Synthesizer) -> None:
with synthesizer:
pass
with pytest.raises(VoicevoxError, match="^The `Synthesizer` is closed$"):
with pytest.raises(ValueError, match="^The `Synthesizer` is closed$"):
_ = synthesizer.metas


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import tempfile
from uuid import UUID

import pydantic
import pytest
import voicevox_core # noqa: F401

Expand Down Expand Up @@ -69,7 +70,7 @@ async def test_user_dict_load() -> None:
assert uuid_c in dict_a.words

# 単語のバリデーション
with pytest.raises(voicevox_core.VoicevoxError):
with pytest.raises(pydantic.ValidationError):
dict_a.add_word(
voicevox_core.UserDictWord(
surface="",
Expand Down
38 changes: 36 additions & 2 deletions crates/voicevox_core_python_api/python/voicevox_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,28 @@
UserDictWordType,
)
from ._rust import ( # noqa: F401
ExtractFullContextLabelError,
GetSupportedDevicesError,
GpuSupportError,
InferenceFailedError,
InvalidModelDataError,
InvalidWordError,
LoadUserDictError,
ModelAlreadyLoadedError,
ModelNotFoundError,
NotLoadedOpenjtalkDictError,
OpenJtalk,
OpenZipFileError,
ParseKanaError,
ReadZipEntryError,
SaveUserDictError,
StyleAlreadyLoadedError,
StyleNotFoundError,
Synthesizer,
UserDict,
UseUserDictError,
VoiceModel,
VoicevoxError,
WordNotFoundError,
__version__,
supported_devices,
)
Expand All @@ -26,15 +43,32 @@
"AccelerationMode",
"AccentPhrase",
"AudioQuery",
"ExtractFullContextLabelError",
"GetSupportedDevicesError",
"GpuSupportError",
"InferenceFailedError",
"InvalidModelDataError",
"InvalidWordError",
"LoadUserDictError",
"ModelAlreadyLoadedError",
"ModelNotFoundError",
"Mora",
"NotLoadedOpenjtalkDictError",
"OpenJtalk",
"OpenZipFileError",
"ParseKanaError",
"ReadZipEntryError",
"SaveUserDictError",
"SpeakerMeta",
"StyleAlreadyLoadedError",
"StyleNotFoundError",
"SupportedDevices",
"Synthesizer",
"VoicevoxError",
"VoiceModel",
"supported_devices",
"UseUserDictError",
"UserDict",
"UserDictWord",
"UserDictWordType",
"WordNotFoundError",
]
89 changes: 87 additions & 2 deletions crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -426,8 +426,93 @@ class UserDict:
"""
...

class VoicevoxError(Exception):
"""VOICEVOX COREのエラー。"""
class NotLoadedOpenjtalkDictError(Exception):
"""open_jtalk辞書ファイルが読み込まれていない。"""

...

class GpuSupportError(Exception):
"""GPUモードがサポートされていない。"""

...

class OpenZipFileError(Exception):
"""ZIPファイルを開くことに失敗した。"""

...

class ReadZipEntryError(Exception):
"""ZIP内のファイルが読めなかった。"""

...

class ModelAlreadyLoadedError(Exception):
"""すでに読み込まれている音声モデルを読み込もうとした。"""

...

class StyleAlreadyLoadedError(Exception):
"""すでに読み込まれているスタイルを読み込もうとした。"""

...

class InvalidModelDataError(Exception):
"""無効なモデルデータ。"""

...

class GetSupportedDevicesError(Exception):
"""サポートされているデバイス情報取得に失敗した。"""

...

class StyleNotFoundError(Exception):
"""スタイルIDに対するスタイルが見つからなかった。"""

...

class ModelNotFoundError(Exception):
"""音声モデルIDに対する音声モデルが見つからなかった。"""

...

class InferenceFailedError(Exception):
"""推論に失敗した。"""

...

class ExtractFullContextLabelError(Exception):
"""コンテキストラベル出力に失敗した。"""

...

class ParseKanaError(ValueError):
"""AquesTalk風記法のテキストの解析に失敗した。"""

...

class LoadUserDictError(Exception):
"""ユーザー辞書を読み込めなかった。"""

...

class SaveUserDictError(Exception):
"""ユーザー辞書を書き込めなかった。"""

...

class WordNotFoundError(Exception):
"""ユーザー辞書に単語が見つからなかった。"""

...

class UseUserDictError(Exception):
"""OpenJTalkのユーザー辞書の設定に失敗した。"""

...

class InvalidWordError(ValueError):
"""ユーザー辞書の単語のバリデーションに失敗した。"""

...

Expand Down
93 changes: 78 additions & 15 deletions crates/voicevox_core_python_api/src/convert.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
use crate::VoicevoxError;
use std::{fmt::Display, future::Future, path::PathBuf};
use std::{error::Error as _, future::Future, iter, path::PathBuf};

use easy_ext::ext;
use pyo3::{types::PyList, FromPyObject as _, PyAny, PyObject, PyResult, Python, ToPyObject};
use pyo3::{
exceptions::{PyException, PyValueError},
types::PyList,
FromPyObject as _, PyAny, PyObject, PyResult, Python, ToPyObject,
};
use serde::{de::DeserializeOwned, Serialize};
use serde_json::json;
use uuid::Uuid;
use voicevox_core::{
AccelerationMode, AccentPhraseModel, StyleId, UserDictWordType, VoiceModelMeta,
};

use crate::{
ExtractFullContextLabelError, GetSupportedDevicesError, GpuSupportError, InferenceFailedError,
InvalidModelDataError, InvalidWordError, LoadUserDictError, ModelAlreadyLoadedError,
ModelNotFoundError, NotLoadedOpenjtalkDictError, OpenZipFileError, ParseKanaError,
ReadZipEntryError, SaveUserDictError, StyleAlreadyLoadedError, StyleNotFoundError,
UseUserDictError, WordNotFoundError,
};

pub fn from_acceleration_mode(ob: &PyAny) -> PyResult<AccelerationMode> {
let py = ob.py();

Expand All @@ -31,7 +42,7 @@ pub fn from_utf8_path(ob: &PyAny) -> PyResult<String> {
PathBuf::extract(ob)?
.into_os_string()
.into_string()
.map_err(|s| VoicevoxError::new_err(format!("{s:?} cannot be encoded to UTF-8")))
.map_err(|s| PyValueError::new_err(format!("{s:?} cannot be encoded to UTF-8")))
}

pub fn from_dataclass<T: DeserializeOwned>(ob: &PyAny) -> PyResult<T> {
Expand All @@ -42,7 +53,7 @@ pub fn from_dataclass<T: DeserializeOwned>(ob: &PyAny) -> PyResult<T> {
.import("json")?
.call_method1("dumps", (ob,))?
.extract::<String>()?;
serde_json::from_str(json).into_py_result()
serde_json::from_str(json).into_py_value_result()
}

pub fn to_pydantic_voice_model_meta<'py>(
Expand All @@ -63,7 +74,7 @@ pub fn to_pydantic_voice_model_meta<'py>(
pub fn to_pydantic_dataclass(x: impl Serialize, class: &PyAny) -> PyResult<&PyAny> {
let py = class.py();

let x = serde_json::to_string(&x).into_py_result()?;
let x = serde_json::to_string(&x).into_py_value_result()?;
let x = py.import("json")?.call_method1("loads", (x,))?.downcast()?;
class.call((), Some(x))
}
Expand All @@ -86,11 +97,10 @@ where
py,
pyo3_asyncio::tokio::get_current_locals(py)?,
async move {
let replaced_accent_phrases = method(rust_accent_phrases, speaker_id)
.await
.into_py_result()?;
let replaced_accent_phrases = method(rust_accent_phrases, speaker_id).await;
Python::with_gil(|py| {
let replaced_accent_phrases = replaced_accent_phrases
.into_py_result(py)?
.iter()
.map(move |accent_phrase| {
to_pydantic_dataclass(
Expand All @@ -107,7 +117,7 @@ where
}
pub fn to_rust_uuid(ob: &PyAny) -> PyResult<Uuid> {
let uuid = ob.getattr("hex")?.extract::<String>()?;
uuid.parse().into_py_result()
uuid.parse::<Uuid>().into_py_value_result()
}
pub fn to_py_uuid(py: Python, uuid: Uuid) -> PyResult<PyObject> {
let uuid = uuid.hyphenated().to_string();
Expand All @@ -122,7 +132,7 @@ pub fn to_rust_user_dict_word(ob: &PyAny) -> PyResult<voicevox_core::UserDictWor
to_rust_word_type(ob.getattr("word_type")?.extract()?)?,
ob.getattr("priority")?.extract()?,
)
.into_py_result()
.into_py_result(ob.py())
}
pub fn to_py_user_dict_word<'py>(
py: Python<'py>,
Expand All @@ -137,12 +147,65 @@ pub fn to_py_user_dict_word<'py>(
pub fn to_rust_word_type(word_type: &PyAny) -> PyResult<UserDictWordType> {
let name = word_type.getattr("name")?.extract::<String>()?;

serde_json::from_value::<UserDictWordType>(json!(name)).into_py_result()
serde_json::from_value::<UserDictWordType>(json!(name)).into_py_value_result()
}

#[ext]
pub impl<T> voicevox_core::Result<T> {
fn into_py_result(self, py: Python<'_>) -> PyResult<T> {
use voicevox_core::ErrorKind;

self.map_err(|err| {
let msg = err.to_string();
let top = match err.kind() {
ErrorKind::NotLoadedOpenjtalkDict => NotLoadedOpenjtalkDictError::new_err(msg),
ErrorKind::GpuSupport => GpuSupportError::new_err(msg),
ErrorKind::OpenZipFile => OpenZipFileError::new_err(msg),
ErrorKind::ReadZipEntry => ReadZipEntryError::new_err(msg),
ErrorKind::ModelAlreadyLoaded => ModelAlreadyLoadedError::new_err(msg),
ErrorKind::StyleAlreadyLoaded => StyleAlreadyLoadedError::new_err(msg),
ErrorKind::InvalidModelData => InvalidModelDataError::new_err(msg),
ErrorKind::GetSupportedDevices => GetSupportedDevicesError::new_err(msg),
ErrorKind::StyleNotFound => StyleNotFoundError::new_err(msg),
ErrorKind::ModelNotFound => ModelNotFoundError::new_err(msg),
ErrorKind::InferenceFailed => InferenceFailedError::new_err(msg),
ErrorKind::ExtractFullContextLabel => ExtractFullContextLabelError::new_err(msg),
ErrorKind::ParseKana => ParseKanaError::new_err(msg),
ErrorKind::LoadUserDict => LoadUserDictError::new_err(msg),
ErrorKind::SaveUserDict => SaveUserDictError::new_err(msg),
ErrorKind::WordNotFound => WordNotFoundError::new_err(msg),
ErrorKind::UseUserDict => UseUserDictError::new_err(msg),
ErrorKind::InvalidWord => InvalidWordError::new_err(msg),
};

[top]
.into_iter()
.chain(
iter::successors(err.source(), |&source| source.source())
.map(|source| PyException::new_err(source.to_string())),
)
.collect::<Vec<_>>()
.into_iter()
.rev()
.reduce(|prev, source| {
source.set_cause(py, Some(prev));
source
})
.expect("should not be empty")
})
}
}

#[ext]
impl<T> std::result::Result<T, uuid::Error> {
fn into_py_value_result(self) -> PyResult<T> {
self.map_err(|e| PyValueError::new_err(e.to_string()))
}
}

#[ext]
pub impl<T, E: Display> Result<T, E> {
fn into_py_result(self) -> PyResult<T> {
self.map_err(|e| VoicevoxError::new_err(e.to_string()))
impl<T> serde_json::Result<T> {
fn into_py_value_result(self) -> PyResult<T> {
self.map_err(|e| PyValueError::new_err(e.to_string()))
}
}
Loading

0 comments on commit 97e711a

Please sign in to comment.