Skip to content

Attempting to bind pyo3 is impossible unless a new solution for serde versions can be found #410

@Vizonex

Description

@Vizonex

Describe the bug
I'm currently trying to bind rustyscript to python since I had given up on pyduktape3 since It can't seem to perform EMCA6 javascript
which I was building this library to fill a void in yt-dlp and utilize it's newly made ejs library
my goal was finding a suitable replacement without having users needing to install something too large
and allowing users to compile everything in one exe instead of hunting down a large diskspace-eater.

My solution to that problem was this library which I have began binding and so far things have looked good up until I needed to start programming stressful conversions with serde-json. the only known package to do that is serde_pyobject. but it wants me to use a newer version of serde. Having seen #399 This is a problem and I would like a better solution to be implemented.

To Reproduce
My current Cargo.toml file should suffice along with some code snippets of my new library coming soon

[package]
name = "pyrv8"
version = "0.1.0"
edition = "2024"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "pyrv8"
crate-type = ["cdylib"]

[dependencies]
# https://github.com/rscarson/rustyscript/issues/399
serde = "=1.0.219"
serde_json = "=1.0.120"
serde_bytes = "=0.11.15"
pyo3 = {version="0.27.1", features = ["experimental-async", "serde"]}
rustyscript = "0.12.3"

I had to force the Runner to also accept Sync by cramming it into a mutex.

use std::sync::{Mutex, MutexGuard};
use std::marker::{Sync, Send};
use pyo3::exceptions::PyRuntimeError;
use pyo3::prelude::*;
use pyo3::sync::MutexExt;


/// GIL Locked System allowing pyo3 to accept 
/// the unsyncable types
pub struct GIL<T>{
    mt:Mutex<T>
}

impl <T>GIL<T> {
    pub fn new(t: T) -> Self{
        Self { mt: Mutex::new(t) }
    }
    pub fn get(&self) -> PyResult<MutexGuard<'_, T>>{
        Python::attach(|py|
            match self.mt.lock_py_attached(py){
                Ok(r) => {
                    Ok(r)
                }
                Err(e) => {
                    Err(PyRuntimeError::new_err(e.to_string()))
                }
            }
        )
    }
}

unsafe impl <T> Sync for GIL<T>{}
unsafe impl <T> Send for GIL<T>{}

lib.rs

use std::time::Duration;

use pyo3::{exceptions::{PyNotADirectoryError, PyRuntimeError}, prelude::*};
use rustyscript::{Runtime, RuntimeOptions, deno_core::PollEventLoopOptions};
// use ternop::ternary;
// pub mod converters;
pub mod locking;
pub mod converters;
use locking::GIL;
use converters::json_to_pyobject;

#[pyclass]
struct Context{
    runtime:GIL<Runtime>
}



#[pymethods]
impl Context {
    #[new]
    #[pyo3(signature = (timeout=None, max_heap_size=None))]
    pub fn new(timeout:Option<f64>, max_heap_size: Option<usize>) -> PyResult<Self> {
        let mut options = RuntimeOptions::default();
        if let Some(timeout) = timeout{
            options.timeout = Duration::from_secs_f64(timeout);
        }
        options.max_heap_size = max_heap_size;
        match Runtime::new(options){
            Ok(runtime) => {Ok(Self{runtime:GIL::new(runtime)})},
            Err(e) => {Err(PyRuntimeError::new_err(e.to_string()))}
        }
    }
    #[getter]
    pub fn timeout(&self) -> PyResult<f64> {
        Ok(self.runtime.get()?.timeout().as_secs_f64())
    }

    #[getter]
    pub fn current_dir(&self) -> PyResult<String> {
        Ok(self.runtime.get()?.current_dir().to_string_lossy().to_string())
    }

    pub fn set_current_dir(&mut self, path: String) -> PyResult<()>{
        match self.runtime.get()?.set_current_dir(path) {
            Ok(_) => {Ok(())}
            Err(e) => {Err(PyNotADirectoryError::new_err(e.to_string()))}
        }
    }

    // Still being worked on...
    // /// Advances eventloop by a single tick this best used 
    // /// with trio or anyio
    // pub async fn advance_async(&mut self, 
    //     wait_for_inspector: Option<bool>,
    //     pump_v8_message_loop: Option<bool>,
    // ) -> PyResult<bool> {
    //     let mut options= PollEventLoopOptions::default();
    //     if let Some(wait_for_inspector) = wait_for_inspector{
    //         options.wait_for_inspector = wait_for_inspector
    //     }
    //     if let Some(pump_v8_message_loop) = pump_v8_message_loop {
    //         options.pump_v8_message_loop = pump_v8_message_loop;
    //     }
        
    //     match self.runtime.get()?.advance_event_loop_async(options).await {
    //         Ok(b) => {Ok(b)},
    //         Err(e) => {Err(PyRuntimeError::new_err(e.to_string()))}
    //     }
    // }

    /// Advances eventloop by a single tick this best used 
    /// with python asyncio, uvloop, winloop or rloop.
    #[pyo3(signature = (wait_for_inspector=None, pump_v8_message_loop=None))]
    pub fn advance(&mut self, 
        wait_for_inspector: Option<bool>,
        pump_v8_message_loop: Option<bool>,
    ) -> PyResult<bool> {
        let mut options= PollEventLoopOptions::default();
        if let Some(wait_for_inspector) = wait_for_inspector{
            options.wait_for_inspector = wait_for_inspector
        }
        if let Some(pump_v8_message_loop) = pump_v8_message_loop {
            options.pump_v8_message_loop = pump_v8_message_loop;
        }
        match self.runtime.get()?.advance_event_loop(options) {
            Ok(b) => {Ok(b)},
            Err(e) => {Err(PyRuntimeError::new_err(e.to_string()))}
        }
    }

    pub fn eval(&mut self, code: &str) -> PyResult<Py<PyAny>>{
        let result = self.runtime.get()?.eval(code);
        match result {
            Ok(r) => {Ok(json_to_pyobject(r)?)}
            Err(e) => {Err(PyRuntimeError::new_err(e.to_string()))}
        }
    }
}

Expected behavior
I Expected it to compile and work but because of #399's solution it seems I am stuck trying to find another way to convert results off which is not what I was after.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • I work on a Windows 10 laptop

Additional context
Add any other context about the problem here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions