From 796e4e0311af38fd5d2fd02f9186f9921a558d86 Mon Sep 17 00:00:00 2001 From: mc_fdc Date: Mon, 19 Dec 2022 08:19:19 +0000 Subject: [PATCH] add: async support --- Cargo.lock | 2 +- mizu/mizu.pyi | 6 +++++- src/asyncio.rs | 15 +++++++++++++++ src/core.rs | 36 ++++++++++++++++++++++++++++++++---- src/lib.rs | 1 + tests/test_async.py | 10 ++++++++++ 6 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 src/asyncio.rs create mode 100644 tests/test_async.py diff --git a/Cargo.lock b/Cargo.lock index 849e197..a5bb492 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,7 +68,7 @@ dependencies = [ [[package]] name = "mizu" -version = "0.2.0-alpha2" +version = "0.2.0-alpha4" dependencies = [ "pulldown-cmark", "pyo3", diff --git a/mizu/mizu.pyi b/mizu/mizu.pyi index 3d24bbd..ff591cb 100644 --- a/mizu/mizu.pyi +++ b/mizu/mizu.pyi @@ -2,9 +2,13 @@ from typing import Optional from .options import Options +import asyncio + class Mizu: - def __init__(self, options: Options = Options()) -> None: + def __init__( + self, options: Options = Options(), loop_ = asyncio.AbstractEventLoop + ) -> None: ... def parse(self, text: str) -> str: diff --git a/src/asyncio.rs b/src/asyncio.rs new file mode 100644 index 0000000..8a59722 --- /dev/null +++ b/src/asyncio.rs @@ -0,0 +1,15 @@ +use pyo3::prelude::*; + +pub fn set_result(py: Python, loop_: PyObject, future: PyObject, result: String) -> PyResult<()> { + loop_.call_method1( + py, + "call_soon_threadsafe", + (future.getattr(py, "set_result")?, result), + )?; + Ok(()) +} + +pub fn create_future(py: Python, loop_: PyObject) -> PyResult { + let future = loop_.call_method0(py, "create_future")?; + Ok(future) +} diff --git a/src/core.rs b/src/core.rs index b80d9e7..5c3f0f4 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,24 +1,31 @@ use pyo3::prelude::*; +use crate::asyncio::*; use pulldown_cmark::{html, Options, Parser}; - /// Markdown parser. /// /// Args: /// options (Options): Options for parser. #[pyclass] -#[pyo3(text_signature = "(options, /)")] +#[pyo3(text_signature = "(options, loop/)")] pub struct Mizu { options: Options, + loop_: Option, } #[pymethods] impl Mizu { #[new] #[args(options = "Options::empty()")] - pub fn new(#[pyo3(from_py_with = "get_options")] options: Options) -> Self { - Mizu { options: options } + pub fn new( + #[pyo3(from_py_with = "get_options")] options: Options, + loop_: Option, + ) -> Self { + Mizu { + options: options, + loop_: loop_, + } } /// Parse markdown text to html. @@ -34,6 +41,27 @@ impl Mizu { html::push_html(&mut output, parser); Ok(output) } + + fn aioparse(&self, py: Python, text: String) -> PyResult { + if self.loop_.is_none() { + return Err(pyo3::exceptions::PyValueError::new_err( + "Event loop is not set", + )); + } + let future = create_future(py, self.loop_.clone().unwrap())?; + let options = self.options.clone(); + let fut_clone = future.clone_ref(py); + let loop_ = self.loop_.clone().unwrap(); + std::thread::spawn(move || { + Python::with_gil(|py| { + let parser: Parser = Parser::new_ext(text.as_str(), options); + let mut output: String = String::new(); + html::push_html(&mut output, parser); + set_result(py, loop_, fut_clone, output).unwrap(); + }); + }); + Ok(future) + } } fn get_options(ob: &PyAny) -> PyResult { diff --git a/src/lib.rs b/src/lib.rs index ecb8f5f..2d350da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ use pyo3::prelude::*; +mod asyncio; mod core; /// Mizu's core diff --git a/tests/test_async.py b/tests/test_async.py new file mode 100644 index 0000000..080a458 --- /dev/null +++ b/tests/test_async.py @@ -0,0 +1,10 @@ +import pytest +from mizu import Mizu + +import asyncio + + +@pytest.mark.asyncio +async def test_parse(): + m = Mizu(loop_=asyncio.get_running_loop()) + assert await m.aioparse("# hello") == "

hello

\n" \ No newline at end of file