awestlake87 / pyo3-asyncio

Other
312 stars 48 forks source link

How to return a bytes type for Python use future_into_py? #67

Closed fcfangcc closed 2 years ago

fcfangcc commented 2 years ago

🌍 Environment

create testlib with maturin

maturin new --bindings pyo3 --mixed testlib

lib.rs

use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyString, PyType};

#[pyclass]
pub struct A {}

#[pymethods]
impl A {
    pub fn get_bytes<'p>(&self, py: Python<'p>) -> &'p PyBytes {
        PyBytes::new(py, b"Hello, world!")
    }

    pub fn get_vecu8_async<'p>(&self, py: Python<'p>) -> PyResult<&'p PyAny> {
        pyo3_asyncio::tokio::future_into_py(py, async move { Ok(b"Hello, world!".to_vec()) })
    }

    // this code is error
    // How can I write to return a bytes to Python with future
    pub fn get_bytes_async<'p>(&self, py: Python<'p>) -> PyResult<&'p PyAny> {
        pyo3_asyncio::tokio::future_into_py(py, async move {
            Ok(PyBytes::new(py, b"Hello, world!").to_object(py))
        })
    }
}

#[pymodule]
fn testlib(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<A>()?;
    Ok(())
}

test.py

import asyncio
import random

from testlib import A

async def test():
    a= A()
    print(a.get_bytes()) 
    # return b"Hello, world!"
    print(await a.get_vecu8_async()) 
    # return [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]
    # print(await a.get_bytes_async()) 
    #  i want it return b"Hello, world!"

asyncio.run(test())

Please also write what exact flags are required to reproduce your results.

awestlake87 commented 2 years ago

Looks like the problem was not with the PyBytes type, but rather capturing the GIL guard py in your async block. Here's what I was able to do to get this working:

lib.rs

use pyo3::prelude::*;
use pyo3::types::PyBytes;

#[pyclass]
pub struct A {}

#[pymethods]
impl A {
    #[new]
    pub fn new() -> Self {
        Self {}
    }

    pub fn get_bytes<'p>(&self, py: Python<'p>) -> &'p PyBytes {
        PyBytes::new(py, b"Hello, world!")
    }

    pub fn get_vecu8_async<'p>(&self, py: Python<'p>) -> PyResult<&'p PyAny> {
        pyo3_asyncio::tokio::future_into_py(py, async move { Ok(b"Hello, world!".to_vec()) })
    }

    pub fn get_bytes_async<'p>(&self, py: Python<'p>) -> PyResult<&'p PyAny> {
        // We need py here to create the future, but we do not want to capture py since it is !Send
        //
        // Instead, we want to create the future and release the GIL, then acquire the GIL in our
        // future to create the bytes
        pyo3_asyncio::tokio::future_into_py(py, async {
            Python::with_gil(|py| Ok(PyBytes::new(py, b"Hello, world!").to_object(py)))
        })
    }
}

/// A Python module implemented in Rust.
#[pymodule]
fn testlib(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<A>()?;
    Ok(())
}

test.py

import pyo3_bytes

import asyncio
import random

from testlib import A

async def test():
    a= A()
    print(a.get_bytes()) 
    # return b"Hello, world!"
    print(await a.get_vecu8_async()) 
    # return [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]
    print(await a.get_bytes_async()) 
    # i want it return b"Hello, world!"

asyncio.run(test())
fcfangcc commented 2 years ago

It works,thanks