Skip to content

Commit

Permalink
[WIP] feat: [Python] PyO3 をv0.23に上げ、pyo3-asyncioを外す
Browse files Browse the repository at this point in the history
  • Loading branch information
qryxip committed Feb 3, 2025
1 parent 78a92a9 commit 44c8bdf
Show file tree
Hide file tree
Showing 5 changed files with 343 additions and 445 deletions.
46 changes: 16 additions & 30 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,8 @@ pollster = "0.3.0"
predicates = "3.1.2"
pretty_assertions = "1.4.1"
proc-macro2 = "1.0.86"
pyo3 = "0.20.3"
pyo3-asyncio = "0.20.0"
pyo3-log = "0.9.0"
pyo3 = "0.23.4"
pyo3-log = "0.12.1"
quote = "1.0.37"
rayon = "1.10.0"
ref-cast = "1.0.23"
Expand Down
3 changes: 1 addition & 2 deletions crates/voicevox_core_python_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ easy-ext.workspace = true
futures-lite.workspace = true
log.workspace = true
once_cell.workspace = true
pyo3 = { workspace = true, features = ["abi3-py310", "extension-module"] }
pyo3-asyncio = { workspace = true, features = ["tokio-runtime"] }
pyo3 = { workspace = true, features = ["experimental-async", "abi3-py310", "extension-module", "py-clone"] }
pyo3-log.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
Expand Down
121 changes: 60 additions & 61 deletions crates/voicevox_core_python_api/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use camino::Utf8PathBuf;
use easy_ext::ext;
use pyo3::{
exceptions::{PyException, PyRuntimeError, PyValueError},
types::{IntoPyDict as _, PyList, PyString},
FromPyObject as _, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject,
types::{IntoPyDict as _, PyAnyMethods as _, PyList, PyString},
Bound, FromPyObject as _, IntoPyObject, Py, PyAny, PyResult, Python,
};
use serde::{de::DeserializeOwned, Serialize};
use serde_json::json;
Expand All @@ -20,7 +20,7 @@ use crate::{
StyleNotFoundError, UseUserDictError, WordNotFoundError,
};

pub(crate) fn from_acceleration_mode(ob: &PyAny) -> PyResult<AccelerationMode> {
pub(crate) fn from_acceleration_mode(ob: &Bound<'_, PyAny>) -> PyResult<AccelerationMode> {
match ob.extract::<&str>()? {
"AUTO" => Ok(AccelerationMode::Auto),
"CPU" => Ok(AccelerationMode::Cpu),
Expand All @@ -32,15 +32,15 @@ pub(crate) fn from_acceleration_mode(ob: &PyAny) -> PyResult<AccelerationMode> {
}
}

pub(crate) fn from_utf8_path(ob: &PyAny) -> PyResult<Utf8PathBuf> {
PathBuf::extract(ob)?
pub(crate) fn from_utf8_path(ob: &Bound<'_, PyAny>) -> PyResult<Utf8PathBuf> {
PathBuf::extract_bound(ob)?
.into_os_string()
.into_string()
.map(Utf8PathBuf::from)
.map_err(|s| PyValueError::new_err(format!("{s:?} cannot be encoded to UTF-8")))
}

pub(crate) fn from_dataclass<T: DeserializeOwned>(ob: &PyAny) -> PyResult<T> {
pub(crate) fn from_dataclass<T: DeserializeOwned>(ob: &Bound<'_, PyAny>) -> PyResult<T> {
let py = ob.py();

let ob = py.import("dataclasses")?.call_method1("asdict", (ob,))?;
Expand All @@ -54,36 +54,37 @@ pub(crate) fn from_dataclass<T: DeserializeOwned>(ob: &PyAny) -> PyResult<T> {
pub(crate) fn to_pydantic_voice_model_meta<'py>(
metas: &VoiceModelMeta,
py: Python<'py>,
) -> PyResult<&'py PyList> {
let class = py
.import("voicevox_core")?
.getattr("CharacterMeta")?
.downcast()?;
) -> PyResult<Bound<'py, PyList>> {
let class = &py.import("voicevox_core")?.getattr("CharacterMeta")?;

let metas = metas
.iter()
.map(|m| to_pydantic_dataclass(m, class))
.collect::<PyResult<Vec<_>>>()?;
Ok(PyList::new(py, metas))
PyList::new(py, metas)
}

pub(crate) fn to_pydantic_dataclass(x: impl Serialize, class: &PyAny) -> PyResult<&PyAny> {
pub(crate) fn to_pydantic_dataclass<'py>(
x: impl Serialize,
class: &Bound<'py, PyAny>,
) -> PyResult<Bound<'py, PyAny>> {
let py = class.py();

let x = serde_json::to_string(&x).into_py_value_result()?;
let x = py.import("json")?.call_method1("loads", (x,))?.downcast()?;
let x = py.import("json")?.call_method1("loads", (x,))?;
let x = x.downcast()?;
class.call((), Some(x))
}

pub(crate) fn blocking_modify_accent_phrases<'py>(
accent_phrases: &'py PyList,
accent_phrases: &Bound<'py, PyList>,
speaker_id: StyleId,
py: Python<'py>,
method: impl FnOnce(Vec<AccentPhrase>, StyleId) -> voicevox_core::Result<Vec<AccentPhrase>>,
) -> PyResult<Vec<&'py PyAny>> {
) -> PyResult<Vec<Bound<'py, PyAny>>> {
let rust_accent_phrases = accent_phrases
.iter()
.map(from_dataclass)
.try_iter()?
.map(|x| x.and_then(|x| from_dataclass(&x)))
.collect::<PyResult<Vec<AccentPhrase>>>()?;

method(rust_accent_phrases, speaker_id)
Expand All @@ -92,58 +93,54 @@ pub(crate) fn blocking_modify_accent_phrases<'py>(
.map(move |accent_phrase| {
to_pydantic_dataclass(
accent_phrase,
py.import("voicevox_core")?.getattr("AccentPhrase")?,
&py.import("voicevox_core")?.getattr("AccentPhrase")?,
)
})
.collect()
}

pub(crate) fn async_modify_accent_phrases<'py, Fun, Fut>(
accent_phrases: &'py PyList,
pub(crate) async fn async_modify_accent_phrases<Fun, Fut>(
accent_phrases: Py<PyList>,
speaker_id: StyleId,
py: Python<'py>,
method: Fun,
) -> PyResult<&'py PyAny>
) -> PyResult<Py<PyList>>
where
Fun: FnOnce(Vec<AccentPhrase>, StyleId) -> Fut + Send + 'static,
Fut: Future<Output = PyResult<Vec<AccentPhrase>>> + Send + 'static,
{
let rust_accent_phrases = accent_phrases
.iter()
.map(from_dataclass)
.collect::<PyResult<Vec<AccentPhrase>>>()?;
pyo3_asyncio::tokio::future_into_py_with_locals(
py,
pyo3_asyncio::tokio::get_current_locals(py)?,
async move {
let replaced_accent_phrases = method(rust_accent_phrases, speaker_id).await?;
Python::with_gil(|py| {
let replaced_accent_phrases = replaced_accent_phrases
.iter()
.map(move |accent_phrase| {
to_pydantic_dataclass(
accent_phrase,
py.import("voicevox_core")?.getattr("AccentPhrase")?,
)
})
.collect::<PyResult<Vec<_>>>()?;
let replaced_accent_phrases = PyList::new(py, replaced_accent_phrases);
Ok(replaced_accent_phrases.to_object(py))
let rust_accent_phrases = Python::with_gil(|py| {
accent_phrases
.into_bound(py)
.try_iter()?
.map(|x| x.and_then(|x| from_dataclass(&x)))
.collect::<PyResult<Vec<AccentPhrase>>>()
})?;
let replaced_accent_phrases = method(rust_accent_phrases, speaker_id).await?;
Python::with_gil(|py| {
let replaced_accent_phrases = replaced_accent_phrases
.iter()
.map(move |accent_phrase| {
to_pydantic_dataclass(
accent_phrase,
&py.import("voicevox_core")?.getattr("AccentPhrase")?,
)
})
},
)
.collect::<PyResult<Vec<_>>>()?;
PyList::new(py, replaced_accent_phrases).map(Into::into)
})
}

pub(crate) fn to_rust_uuid(ob: &PyAny) -> PyResult<Uuid> {
pub(crate) fn to_rust_uuid(ob: &Bound<'_, PyAny>) -> PyResult<Uuid> {
let uuid = ob.getattr("hex")?.extract::<String>()?;
uuid.parse::<Uuid>().into_py_value_result()
}
pub(crate) fn to_py_uuid(py: Python<'_>, uuid: Uuid) -> PyResult<PyObject> {
pub(crate) fn to_py_uuid(py: Python<'_>, uuid: Uuid) -> PyResult<Bound<'_, PyAny>> {
let uuid = uuid.hyphenated().to_string();
let uuid = py.import("uuid")?.call_method1("UUID", (uuid,))?;
Ok(uuid.to_object(py))
py.import("uuid")?.call_method1("UUID", (uuid,))
}
pub(crate) fn to_rust_user_dict_word(ob: &PyAny) -> PyResult<voicevox_core::UserDictWord> {
pub(crate) fn to_rust_user_dict_word(
ob: &Bound<'_, PyAny>,
) -> PyResult<voicevox_core::UserDictWord> {
voicevox_core::UserDictWord::new(
ob.getattr("surface")?.extract()?,
ob.getattr("pronunciation")?.extract()?,
Expand All @@ -156,12 +153,9 @@ pub(crate) fn to_rust_user_dict_word(ob: &PyAny) -> PyResult<voicevox_core::User
pub(crate) fn to_py_user_dict_word<'py>(
py: Python<'py>,
word: &voicevox_core::UserDictWord,
) -> PyResult<&'py PyAny> {
let class = py
.import("voicevox_core")?
.getattr("UserDictWord")?
.downcast()?;
to_pydantic_dataclass(word, class)
) -> PyResult<Bound<'py, PyAny>> {
let class = py.import("voicevox_core")?.getattr("UserDictWord")?;
to_pydantic_dataclass(word, class.downcast()?)
}
fn from_literal_choice<T: DeserializeOwned>(s: &str) -> PyResult<T> {
serde_json::from_value::<T>(json!(s)).into_py_value_result()
Expand All @@ -175,8 +169,12 @@ fn from_literal_choice<T: DeserializeOwned>(s: &str) -> PyResult<T> {
///
/// return f(x)
/// ```
pub(crate) fn ready(x: impl IntoPy<PyObject>, py: Python<'_>) -> PyResult<&PyAny> {
pub(crate) fn ready<'py>(
x: impl IntoPyObject<'py>,
py: Python<'py>,
) -> PyResult<Bound<'py, PyAny>> {
// ```py
// import asyncio
// from asyncio import Future
//
// running_loop = asyncio.get_running_loop()
Expand All @@ -185,10 +183,11 @@ pub(crate) fn ready(x: impl IntoPy<PyObject>, py: Python<'_>) -> PyResult<&PyAny
// return fut
// ```

let asyncio_future = py.import("asyncio")?.getattr("Future")?;
let asyncio = py.import("asyncio")?;
let asyncio_future = asyncio.getattr("Future")?;

let running_loop = pyo3_asyncio::get_running_loop(py)?;
let fut = asyncio_future.call((), Some([("loop", running_loop)].into_py_dict(py)))?;
let running_loop = asyncio.call_method0("get_running_loop")?;
let fut = asyncio_future.call((), Some(&[("loop", running_loop)].into_py_dict(py)?))?;
fut.call_method1("set_result", (x,))?;
Ok(fut)
}
Expand Down
Loading

0 comments on commit 44c8bdf

Please sign in to comment.