From 9fdb347824ee9266b8128d3cb20334188dcf672b Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Tue, 24 Sep 2024 16:52:53 +0900 Subject: [PATCH 1/2] =?UTF-8?q?#830=20=E3=81=AE=E8=A8=AD=E8=A8=88=E3=82=92?= =?UTF-8?q?`UserDict`=E3=81=AB=E3=82=82=20(#834)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `UserDict`に対して以下の二つを行う。 1. #830 のように`Inner`で共通化する設計にする 2. async-fsを導入することでasync APIとしての質を向上させる --- crates/voicevox_core/Cargo.toml | 2 +- crates/voicevox_core/src/asyncs.rs | 20 ++ crates/voicevox_core/src/user_dict/dict.rs | 204 +++++++++++++-------- 3 files changed, 153 insertions(+), 73 deletions(-) diff --git a/crates/voicevox_core/Cargo.toml b/crates/voicevox_core/Cargo.toml index d5fb28321..8390e1de3 100644 --- a/crates/voicevox_core/Cargo.toml +++ b/crates/voicevox_core/Cargo.toml @@ -16,7 +16,7 @@ link-onnxruntime = [] [dependencies] anyhow.workspace = true -async-fs.workspace = true # 今これを使っている箇所はどこにも無いが、`UserDict`にはこれを使った方がよいはず +async-fs.workspace = true async-lock.workspace = true async_zip = { workspace = true, features = ["deflate"] } blocking.workspace = true diff --git a/crates/voicevox_core/src/asyncs.rs b/crates/voicevox_core/src/asyncs.rs index d89aa7d4b..24fdd82b4 100644 --- a/crates/voicevox_core/src/asyncs.rs +++ b/crates/voicevox_core/src/asyncs.rs @@ -34,6 +34,10 @@ pub(crate) trait Async: 'static { /// `io::Error`は素(`i32`相当)のままにしておき、この関数を呼び出す側でfs-err風のメッセージを付 /// ける。 async fn open_file_ro(path: impl AsRef) -> io::Result; + + async fn read(path: impl AsRef) -> io::Result>; + + async fn write(path: impl AsRef, content: impl AsRef<[u8]>) -> io::Result<()>; } pub(crate) trait Mutex: From + Send + Sync + Unpin { @@ -59,6 +63,14 @@ impl Async for SingleTasked { async fn open_file_ro(path: impl AsRef) -> io::Result { std::fs::File::open(path).map(StdFile) } + + async fn read(path: impl AsRef) -> io::Result> { + std::fs::read(path) + } + + async fn write(path: impl AsRef, content: impl AsRef<[u8]>) -> io::Result<()> { + std::fs::write(path, content) + } } pub(crate) struct StdMutex(std::sync::Mutex); @@ -111,6 +123,14 @@ impl Async for BlockingThreadPool { async fn open_file_ro(path: impl AsRef) -> io::Result { AsyncRoFile::open(path).await } + + async fn read(path: impl AsRef) -> io::Result> { + async_fs::read(path).await + } + + async fn write(path: impl AsRef, content: impl AsRef<[u8]>) -> io::Result<()> { + async_fs::write(path, content).await + } } impl Mutex for async_lock::Mutex { diff --git a/crates/voicevox_core/src/user_dict/dict.rs b/crates/voicevox_core/src/user_dict/dict.rs index 0e1c89ca2..2f851b138 100644 --- a/crates/voicevox_core/src/user_dict/dict.rs +++ b/crates/voicevox_core/src/user_dict/dict.rs @@ -1,33 +1,128 @@ -// TODO: `VoiceModelFile`のように、次のような設計にする。 -// -// ``` -// pub(crate) mod blocking { -// pub struct UserDict(Inner); -// // … -// } -// pub(crate) mod nonblocking { -// pub struct UserDict(Inner); -// // … -// } -// ``` +use std::{marker::PhantomData, path::Path}; + +use anyhow::Context as _; +use easy_ext::ext; +use educe::Educe; +use indexmap::IndexMap; +use itertools::Itertools as _; +use uuid::Uuid; + +use crate::{asyncs::Async, error::ErrorRepr}; + +use super::UserDictWord; + +#[derive(Educe)] +#[educe(Default(bound = "A:"))] +#[educe(Debug(bound = "A:"))] +struct Inner { + words: std::sync::Mutex>, + _marker: PhantomData, +} + +impl Inner { + fn to_json(&self) -> String { + self.with_words(|words| serde_json::to_string(words).expect("should not fail")) + } + + fn with_words(&self, f: F) -> R + where + F: FnOnce(&mut IndexMap) -> R, + { + f(&mut self.words.lock().unwrap_or_else(|e| panic!("{e}"))) + } + + async fn load(&self, store_path: &str) -> crate::Result<()> { + let words = async { + let words = &A::fs_err_read(store_path).await?; + let words = serde_json::from_slice::>(words)?; + Ok(words) + } + .await + .map_err(ErrorRepr::LoadUserDict)?; + + self.with_words(|words_| words_.extend(words)); + Ok(()) + } + + fn add_word(&self, word: UserDictWord) -> crate::Result { + let word_uuid = Uuid::new_v4(); + self.with_words(|word_| word_.insert(word_uuid, word)); + Ok(word_uuid) + } + + fn update_word(&self, word_uuid: Uuid, new_word: UserDictWord) -> crate::Result<()> { + self.with_words(|words| { + if !words.contains_key(&word_uuid) { + return Err(ErrorRepr::WordNotFound(word_uuid).into()); + } + words.insert(word_uuid, new_word); + Ok(()) + }) + } + + fn remove_word(&self, word_uuid: Uuid) -> crate::Result { + let Some(word) = self.with_words(|words| words.remove(&word_uuid)) else { + return Err(ErrorRepr::WordNotFound(word_uuid).into()); + }; + Ok(word) + } + + fn import(&self, other: &Self) -> crate::Result<()> { + self.with_words(|self_words| { + other.with_words(|other_words| { + for (word_uuid, word) in other_words { + self_words.insert(*word_uuid, word.clone()); + } + Ok(()) + }) + }) + } + + async fn save(&self, store_path: &str) -> crate::Result<()> { + A::fs_err_write( + store_path, + serde_json::to_vec(&self.words).expect("should not fail"), + ) + .await + .map_err(ErrorRepr::SaveUserDict) + .map_err(Into::into) + } + + fn to_mecab_format(&self) -> String { + self.with_words(|words| words.values().map(UserDictWord::to_mecab_format).join("\n")) + } +} + +#[ext] +impl A { + async fn fs_err_read(path: impl AsRef) -> anyhow::Result> { + Self::read(&path) + .await + .with_context(|| format!("failed to read from file `{}`", path.as_ref().display())) + } + + async fn fs_err_write(path: impl AsRef, content: impl AsRef<[u8]>) -> anyhow::Result<()> { + Self::write(&path, content) + .await + .with_context(|| format!("failed to write to file `{}`", path.as_ref().display())) + } +} pub(crate) mod blocking { use indexmap::IndexMap; - use itertools::join; use uuid::Uuid; - use crate::{error::ErrorRepr, Result}; + use crate::{asyncs::SingleTasked, future::FutureExt as _, Result}; - use super::super::word::UserDictWord; + use super::{super::word::UserDictWord, Inner}; /// ユーザー辞書。 /// /// 単語はJSONとの相互変換のために挿入された順序を保つ。 #[derive(Debug, Default)] - pub struct UserDict { - words: std::sync::Mutex>, - } + pub struct UserDict(Inner); + // TODO: 引数の`path`は全部`AsRef`にする impl self::UserDict { /// ユーザー辞書を作成する。 pub fn new() -> Self { @@ -35,11 +130,12 @@ pub(crate) mod blocking { } pub fn to_json(&self) -> String { - serde_json::to_string(&*self.words.lock().unwrap()).expect("should not fail") + self.0.to_json() } + // TODO: `&mut IndexMap<_>`を取れるようにする pub fn with_words(&self, f: impl FnOnce(&IndexMap) -> R) -> R { - f(&self.words.lock().unwrap()) + self.0.with_words(|words| f(words)) } /// ユーザー辞書をファイルから読み込む。 @@ -48,82 +144,48 @@ pub(crate) mod blocking { /// /// ファイルが読めなかった、または内容が不正だった場合はエラーを返す。 pub fn load(&self, store_path: &str) -> Result<()> { - let words = (|| { - let words = &fs_err::read(store_path)?; - let words = serde_json::from_slice::>(words)?; - Ok(words) - })() - .map_err(ErrorRepr::LoadUserDict)?; - - self.words.lock().unwrap().extend(words); - Ok(()) + self.0.load(store_path).block_on() } /// ユーザー辞書に単語を追加する。 pub fn add_word(&self, word: UserDictWord) -> Result { - let word_uuid = Uuid::new_v4(); - self.words.lock().unwrap().insert(word_uuid, word); - Ok(word_uuid) + self.0.add_word(word) } /// ユーザー辞書の単語を変更する。 pub fn update_word(&self, word_uuid: Uuid, new_word: UserDictWord) -> Result<()> { - let mut words = self.words.lock().unwrap(); - if !words.contains_key(&word_uuid) { - return Err(ErrorRepr::WordNotFound(word_uuid).into()); - } - words.insert(word_uuid, new_word); - Ok(()) + self.0.update_word(word_uuid, new_word) } /// ユーザー辞書から単語を削除する。 pub fn remove_word(&self, word_uuid: Uuid) -> Result { - let Some(word) = self.words.lock().unwrap().remove(&word_uuid) else { - return Err(ErrorRepr::WordNotFound(word_uuid).into()); - }; - Ok(word) + self.0.remove_word(word_uuid) } /// 他のユーザー辞書をインポートする。 pub fn import(&self, other: &Self) -> Result<()> { - for (word_uuid, word) in &*other.words.lock().unwrap() { - self.words.lock().unwrap().insert(*word_uuid, word.clone()); - } - Ok(()) + self.0.import(&other.0) } /// ユーザー辞書を保存する。 pub fn save(&self, store_path: &str) -> Result<()> { - fs_err::write( - store_path, - serde_json::to_vec(&self.words).expect("should not fail"), - ) - .map_err(|e| ErrorRepr::SaveUserDict(e.into()).into()) + self.0.save(store_path).block_on() } /// MeCabで使用する形式に変換する。 pub(crate) fn to_mecab_format(&self) -> String { - join( - self.words - .lock() - .unwrap() - .values() - .map(UserDictWord::to_mecab_format), - "\n", - ) + self.0.to_mecab_format() } } } pub(crate) mod nonblocking { - use std::sync::Arc; - use indexmap::IndexMap; use uuid::Uuid; - use crate::Result; + use crate::{asyncs::BlockingThreadPool, Result}; - use super::super::word::UserDictWord; + use super::{super::word::UserDictWord, Inner}; /// ユーザー辞書。 /// @@ -136,20 +198,22 @@ pub(crate) mod nonblocking { /// [blocking]: https://docs.rs/crate/blocking /// [`nonblocking`モジュールのドキュメント]: crate::nonblocking #[derive(Debug, Default)] - pub struct UserDict(Arc); + pub struct UserDict(Inner); + // TODO: 引数の`path`は全部`AsRef`にする impl self::UserDict { /// ユーザー辞書を作成する。 pub fn new() -> Self { - Self(super::blocking::UserDict::new().into()) + Default::default() } pub fn to_json(&self) -> String { self.0.to_json() } + // TODO: `&mut IndexMap<_>`を取れるようにする pub fn with_words(&self, f: impl FnOnce(&IndexMap) -> R) -> R { - self.0.with_words(f) + self.0.with_words(|words| f(words)) } /// ユーザー辞書をファイルから読み込む。 @@ -158,9 +222,7 @@ pub(crate) mod nonblocking { /// /// ファイルが読めなかった、または内容が不正だった場合はエラーを返す。 pub async fn load(&self, store_path: &str) -> Result<()> { - let blocking = self.0.clone(); - let store_path = store_path.to_owned(); - crate::task::asyncify(move || blocking.load(&store_path)).await + self.0.load(store_path).await } /// ユーザー辞書に単語を追加する。 @@ -185,9 +247,7 @@ pub(crate) mod nonblocking { /// ユーザー辞書を保存する。 pub async fn save(&self, store_path: &str) -> Result<()> { - let blocking = self.0.clone(); - let store_path = store_path.to_owned(); - crate::task::asyncify(move || blocking.save(&store_path)).await + self.0.save(store_path).await } /// MeCabで使用する形式に変換する。 From 8d603d9b70372c3eaad56d2f9bdf6bcbd78935d1 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Tue, 24 Sep 2024 22:36:30 +0900 Subject: [PATCH 2/2] change: minor changes for `UserDict` API (#835) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `UserDict`のパブリックAPIについて以下の変更を加える。 1. `load`と`store`が引数に取るファイルパスについて: * Rust APIでは`&str`から`impl AsRef`にする * Python APIでは`StrPath`相当にする * Java APIでは`java.io.File`と`java.nio.file.Path`のオーバーロードを 追加する 2. Rust APIの`with_words`から`&mut IndexMap<…>`が得られるようにする `dict`や`java.util.HashMap`へ変換するAPIの改良はfuture workとする。 --- crates/voicevox_core/src/user_dict/dict.rs | 28 ++++++------- .../jp/hiroshiba/voicevoxcore/UserDict.java | 42 +++++++++++++++++++ .../voicevox_core_java_api/src/user_dict.rs | 4 +- .../python/voicevox_core/_rust/asyncio.pyi | 4 +- .../python/voicevox_core/_rust/blocking.pyi | 4 +- .../voicevox_core_python_api/src/convert.rs | 1 - crates/voicevox_core_python_api/src/lib.rs | 10 ++--- 7 files changed, 66 insertions(+), 27 deletions(-) diff --git a/crates/voicevox_core/src/user_dict/dict.rs b/crates/voicevox_core/src/user_dict/dict.rs index 2f851b138..e3bb640c9 100644 --- a/crates/voicevox_core/src/user_dict/dict.rs +++ b/crates/voicevox_core/src/user_dict/dict.rs @@ -31,7 +31,7 @@ impl Inner { f(&mut self.words.lock().unwrap_or_else(|e| panic!("{e}"))) } - async fn load(&self, store_path: &str) -> crate::Result<()> { + async fn load(&self, store_path: impl AsRef) -> crate::Result<()> { let words = async { let words = &A::fs_err_read(store_path).await?; let words = serde_json::from_slice::>(words)?; @@ -78,7 +78,7 @@ impl Inner { }) } - async fn save(&self, store_path: &str) -> crate::Result<()> { + async fn save(&self, store_path: impl AsRef) -> crate::Result<()> { A::fs_err_write( store_path, serde_json::to_vec(&self.words).expect("should not fail"), @@ -109,6 +109,8 @@ impl A { } pub(crate) mod blocking { + use std::path::Path; + use indexmap::IndexMap; use uuid::Uuid; @@ -122,7 +124,6 @@ pub(crate) mod blocking { #[derive(Debug, Default)] pub struct UserDict(Inner); - // TODO: 引数の`path`は全部`AsRef`にする impl self::UserDict { /// ユーザー辞書を作成する。 pub fn new() -> Self { @@ -133,9 +134,8 @@ pub(crate) mod blocking { self.0.to_json() } - // TODO: `&mut IndexMap<_>`を取れるようにする - pub fn with_words(&self, f: impl FnOnce(&IndexMap) -> R) -> R { - self.0.with_words(|words| f(words)) + pub fn with_words(&self, f: impl FnOnce(&mut IndexMap) -> R) -> R { + self.0.with_words(f) } /// ユーザー辞書をファイルから読み込む。 @@ -143,7 +143,7 @@ pub(crate) mod blocking { /// # Errors /// /// ファイルが読めなかった、または内容が不正だった場合はエラーを返す。 - pub fn load(&self, store_path: &str) -> Result<()> { + pub fn load(&self, store_path: impl AsRef) -> Result<()> { self.0.load(store_path).block_on() } @@ -168,7 +168,7 @@ pub(crate) mod blocking { } /// ユーザー辞書を保存する。 - pub fn save(&self, store_path: &str) -> Result<()> { + pub fn save(&self, store_path: impl AsRef) -> Result<()> { self.0.save(store_path).block_on() } @@ -180,6 +180,8 @@ pub(crate) mod blocking { } pub(crate) mod nonblocking { + use std::path::Path; + use indexmap::IndexMap; use uuid::Uuid; @@ -200,7 +202,6 @@ pub(crate) mod nonblocking { #[derive(Debug, Default)] pub struct UserDict(Inner); - // TODO: 引数の`path`は全部`AsRef`にする impl self::UserDict { /// ユーザー辞書を作成する。 pub fn new() -> Self { @@ -211,9 +212,8 @@ pub(crate) mod nonblocking { self.0.to_json() } - // TODO: `&mut IndexMap<_>`を取れるようにする - pub fn with_words(&self, f: impl FnOnce(&IndexMap) -> R) -> R { - self.0.with_words(|words| f(words)) + pub fn with_words(&self, f: impl FnOnce(&mut IndexMap) -> R) -> R { + self.0.with_words(f) } /// ユーザー辞書をファイルから読み込む。 @@ -221,7 +221,7 @@ pub(crate) mod nonblocking { /// # Errors /// /// ファイルが読めなかった、または内容が不正だった場合はエラーを返す。 - pub async fn load(&self, store_path: &str) -> Result<()> { + pub async fn load(&self, store_path: impl AsRef) -> Result<()> { self.0.load(store_path).await } @@ -246,7 +246,7 @@ pub(crate) mod nonblocking { } /// ユーザー辞書を保存する。 - pub async fn save(&self, store_path: &str) -> Result<()> { + pub async fn save(&self, store_path: impl AsRef) -> Result<()> { self.0.save(store_path).await } diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java index 21f6843dd..7135365ff 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java @@ -7,6 +7,8 @@ import jakarta.annotation.Nonnull; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; +import java.io.File; +import java.nio.file.Path; import java.util.HashMap; import jp.hiroshiba.voicevoxcore.exceptions.LoadUserDictException; import jp.hiroshiba.voicevoxcore.exceptions.SaveUserDictException; @@ -70,6 +72,26 @@ public void importDict(UserDict dict) { rsImportDict(dict); } + /** + * ユーザー辞書を読み込む。 + * + * @param path ユーザー辞書のパス。 + * @throws LoadUserDictException ユーザー辞書を読み込めなかった場合。 + */ + public void load(Path path) throws LoadUserDictException { + load(path.toString()); + } + + /** + * ユーザー辞書を読み込む。 + * + * @param path ユーザー辞書のパス。 + * @throws LoadUserDictException ユーザー辞書を読み込めなかった場合。 + */ + public void load(File path) throws LoadUserDictException { + load(path.toString()); + } + /** * ユーザー辞書を読み込む。 * @@ -80,6 +102,26 @@ public void load(String path) throws LoadUserDictException { rsLoad(path); } + /** + * ユーザー辞書を保存する。 + * + * @param path ユーザー辞書のパス。 + * @throws SaveUserDictException ユーザー辞書を保存できなかった場合。 + */ + public void save(Path path) throws SaveUserDictException { + rsSave(path.toString()); + } + + /** + * ユーザー辞書を保存する。 + * + * @param path ユーザー辞書のパス。 + * @throws SaveUserDictException ユーザー辞書を保存できなかった場合。 + */ + public void save(File path) throws SaveUserDictException { + rsSave(path.toString()); + } + /** * ユーザー辞書を保存する。 * diff --git a/crates/voicevox_core_java_api/src/user_dict.rs b/crates/voicevox_core_java_api/src/user_dict.rs index df8b8270b..ceee1f42e 100644 --- a/crates/voicevox_core_java_api/src/user_dict.rs +++ b/crates/voicevox_core_java_api/src/user_dict.rs @@ -124,7 +124,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_UserDict_rsLoad<'local> .clone(); let path = env.get_string(&path)?; - let path = &Cow::from(&path); + let path = &*Cow::from(&path); internal.load(path)?; @@ -144,7 +144,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_UserDict_rsSave<'local> .clone(); let path = env.get_string(&path)?; - let path = &Cow::from(&path); + let path = &*Cow::from(&path); internal.save(path)?; diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi b/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi index b386d2d5a..d6359e038 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi @@ -431,7 +431,7 @@ class UserDict: """このオプジェクトの :class:`dict` としての表現。""" ... def __init__(self) -> None: ... - async def load(self, path: str) -> None: + async def load(self, path: Union[str, PathLike[str]]) -> None: """ファイルに保存されたユーザー辞書を読み込む。 Parameters @@ -440,7 +440,7 @@ class UserDict: ユーザー辞書のパス。 """ ... - async def save(self, path: str) -> None: + async def save(self, path: Union[str, PathLike[str]]) -> None: """ ユーザー辞書をファイルに保存する。 diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_rust/blocking.pyi b/crates/voicevox_core_python_api/python/voicevox_core/_rust/blocking.pyi index 891ceb05e..cf4f1f5c6 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_rust/blocking.pyi +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust/blocking.pyi @@ -426,7 +426,7 @@ class UserDict: """このオプジェクトの :class:`dict` としての表現。""" ... def __init__(self) -> None: ... - def load(self, path: str) -> None: + def load(self, path: Union[str, PathLike[str]]) -> None: """ファイルに保存されたユーザー辞書を読み込む。 Parameters @@ -435,7 +435,7 @@ class UserDict: ユーザー辞書のパス。 """ ... - def save(self, path: str) -> None: + def save(self, path: Union[str, PathLike[str]]) -> None: """ ユーザー辞書をファイルに保存する。 diff --git a/crates/voicevox_core_python_api/src/convert.rs b/crates/voicevox_core_python_api/src/convert.rs index 8152bc980..d4a867606 100644 --- a/crates/voicevox_core_python_api/src/convert.rs +++ b/crates/voicevox_core_python_api/src/convert.rs @@ -37,7 +37,6 @@ pub(crate) fn from_acceleration_mode(ob: &PyAny) -> PyResult { } } -// FIXME: `UserDict`についてはこれではなく、`PathBuf::extract`を直接使うようにする pub(crate) fn from_utf8_path(ob: &PyAny) -> PyResult { PathBuf::extract(ob)? .into_os_string() diff --git a/crates/voicevox_core_python_api/src/lib.rs b/crates/voicevox_core_python_api/src/lib.rs index 00b6e7102..24d28261d 100644 --- a/crates/voicevox_core_python_api/src/lib.rs +++ b/crates/voicevox_core_python_api/src/lib.rs @@ -734,11 +734,11 @@ mod blocking { Self::default() } - fn load(&self, path: &str, py: Python<'_>) -> PyResult<()> { + fn load(&self, path: PathBuf, py: Python<'_>) -> PyResult<()> { self.dict.load(path).into_py_result(py) } - fn save(&self, path: &str, py: Python<'_>) -> PyResult<()> { + fn save(&self, path: PathBuf, py: Python<'_>) -> PyResult<()> { self.dict.save(path).into_py_result(py) } @@ -1363,9 +1363,8 @@ mod asyncio { Self::default() } - fn load<'py>(&self, path: &str, py: Python<'py>) -> PyResult<&'py PyAny> { + fn load<'py>(&self, path: PathBuf, py: Python<'py>) -> PyResult<&'py PyAny> { let this = self.dict.clone(); - let path = path.to_owned(); pyo3_asyncio::tokio::future_into_py(py, async move { let result = this.load(&path).await; @@ -1373,9 +1372,8 @@ mod asyncio { }) } - fn save<'py>(&self, path: &str, py: Python<'py>) -> PyResult<&'py PyAny> { + fn save<'py>(&self, path: PathBuf, py: Python<'py>) -> PyResult<&'py PyAny> { let this = self.dict.clone(); - let path = path.to_owned(); pyo3_asyncio::tokio::future_into_py(py, async move { let result = this.save(&path).await;