Skip to content

Commit 4aef0e7

Browse files
committed
add sync versions
1 parent 1366754 commit 4aef0e7

File tree

1 file changed

+165
-1
lines changed

1 file changed

+165
-1
lines changed

src/lib.rs

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,21 @@ impl PyRunner {
133133
.await
134134
.map(|_| ())
135135
}
136+
137+
/// Synchronously executes a block of Python code.
138+
///
139+
/// This is a blocking wrapper around `run`. It is intended for use in
140+
/// synchronous applications.
141+
///
142+
/// * `code`: A string slice containing the Python code to execute.
143+
///
144+
/// **Note:** Calling this from an existing async runtime can lead to panics.
145+
pub fn run_sync(&self, code: &str) -> Result<(), PyRunnerError> {
146+
let rt =
147+
tokio::runtime::Runtime::new().map_err(|e| PyRunnerError::PyError(e.to_string()))?;
148+
rt.block_on(self.run(code))
149+
}
150+
136151
/// Asynchronously runs a python file.
137152
/// * `file`: Absolute path to a python file to execute.
138153
/// Also loads the path of the file to sys.path for imports.
@@ -142,6 +157,20 @@ impl PyRunner {
142157
.map(|_| ())
143158
}
144159

160+
/// Synchronously runs a python file.
161+
///
162+
/// This is a blocking wrapper around `run_file`. It is intended for use in
163+
/// synchronous applications.
164+
///
165+
/// * `file`: Absolute path to a python file to execute.
166+
///
167+
/// **Note:** Calling this from an existing async runtime can lead to panics.
168+
pub fn run_file_sync(&self, file: &Path) -> Result<(), PyRunnerError> {
169+
let rt =
170+
tokio::runtime::Runtime::new().map_err(|e| PyRunnerError::PyError(e.to_string()))?;
171+
rt.block_on(self.run_file(file))
172+
}
173+
145174
/// Asynchronously evaluates a single Python expression.
146175
///
147176
/// * `code`: A string slice containing the Python expression to evaluate.
@@ -152,6 +181,20 @@ impl PyRunner {
152181
self.send_command(CmdType::EvalCode(code.into())).await
153182
}
154183

184+
/// Synchronously evaluates a single Python expression.
185+
///
186+
/// This is a blocking wrapper around `eval`. It is intended for use in
187+
/// synchronous applications.
188+
///
189+
/// * `code`: A string slice containing the Python expression to evaluate.
190+
///
191+
/// **Note:** Calling this from an existing async runtime can lead to panics.
192+
pub fn eval_sync(&self, code: &str) -> Result<Value, PyRunnerError> {
193+
let rt =
194+
tokio::runtime::Runtime::new().map_err(|e| PyRunnerError::PyError(e.to_string()))?;
195+
rt.block_on(self.eval(code))
196+
}
197+
155198
/// Asynchronously reads a variable from the Python interpreter's global scope.
156199
///
157200
/// * `var_name`: The name of the variable to read. It can be a dot-separated path
@@ -162,6 +205,20 @@ impl PyRunner {
162205
.await
163206
}
164207

208+
/// Synchronously reads a variable from the Python interpreter's global scope.
209+
///
210+
/// This is a blocking wrapper around `read_variable`. It is intended for use in
211+
/// synchronous applications.
212+
///
213+
/// * `var_name`: The name of the variable to read.
214+
///
215+
/// **Note:** Calling this from an existing async runtime can lead to panics.
216+
pub fn read_variable_sync(&self, var_name: &str) -> Result<Value, PyRunnerError> {
217+
let rt =
218+
tokio::runtime::Runtime::new().map_err(|e| PyRunnerError::PyError(e.to_string()))?;
219+
rt.block_on(self.read_variable(var_name))
220+
}
221+
165222
/// Asynchronously calls a Python function in the interpreter's global scope.
166223
///
167224
/// * `name`: The name of the function to call. It can be a dot-separated path
@@ -181,6 +238,27 @@ impl PyRunner {
181238
.await
182239
}
183240

241+
/// Synchronously calls a Python function in the interpreter's global scope.
242+
///
243+
/// This is a blocking wrapper around `call_function`. It will create a new
244+
/// Tokio runtime to execute the async function. It is intended for use in
245+
/// synchronous applications.
246+
///
247+
/// * `name`: The name of the function to call.
248+
/// * `args`: A vector of `serde_json::Value` to pass as arguments to the function.
249+
///
250+
/// **Note:** Calling this from an existing async runtime can lead to panics.
251+
/// This is for calling from a non-async context.
252+
#[cfg(feature = "pyo3")]
253+
pub fn call_function_sync(
254+
&self,
255+
name: &str,
256+
args: Vec<Value>,
257+
) -> Result<Value, PyRunnerError> {
258+
let rt = tokio::runtime::Runtime::new().map_err(|e| PyRunnerError::PyError(e.to_string()))?;
259+
rt.block_on(self.call_function(name, args))
260+
}
261+
184262
/// Asynchronously calls an async Python function in the interpreter's global scope.
185263
///
186264
/// * `name`: The name of the function to call. It can be a dot-separated path
@@ -200,12 +278,45 @@ impl PyRunner {
200278
.await
201279
}
202280

281+
/// Synchronously calls an async Python function in the interpreter's global scope.
282+
///
283+
/// This is a blocking wrapper around `call_async_function`. It is intended for use in
284+
/// synchronous applications.
285+
///
286+
/// * `name`: The name of the function to call.
287+
/// * `args`: A vector of `serde_json::Value` to pass as arguments to the function.
288+
///
289+
/// **Note:** Calling this from an existing async runtime can lead to panics.
290+
#[cfg(feature = "pyo3")]
291+
pub fn call_async_function_sync(
292+
&self,
293+
name: &str,
294+
args: Vec<Value>,
295+
) -> Result<Value, PyRunnerError> {
296+
let rt =
297+
tokio::runtime::Runtime::new().map_err(|e| PyRunnerError::PyError(e.to_string()))?;
298+
rt.block_on(self.call_async_function(name, args))
299+
}
300+
203301
/// Stops the Python execution thread gracefully.
204302
pub async fn stop(&self) -> Result<(), PyRunnerError> {
205303
// We can ignore the `Ok(Value::Null)` result.
206304
self.send_command(CmdType::Stop).await?;
207305
Ok(())
208306
}
307+
308+
/// Synchronously stops the Python execution thread gracefully.
309+
///
310+
/// This is a blocking wrapper around `stop`. It is intended for use in
311+
/// synchronous applications.
312+
///
313+
/// **Note:** Calling this from an existing async runtime can lead to panics.
314+
pub fn stop_sync(&self) -> Result<(), PyRunnerError> {
315+
let rt =
316+
tokio::runtime::Runtime::new().map_err(|e| PyRunnerError::PyError(e.to_string()))?;
317+
rt.block_on(self.stop())
318+
}
319+
209320
/// Set python venv environment folder (does not change interpreter)
210321
pub async fn set_venv(&self, venv_path: &Path) -> Result<(), PyRunnerError> {
211322
if !venv_path.is_dir() {
@@ -239,6 +350,20 @@ impl PyRunner {
239350
))
240351
.await
241352
}
353+
354+
/// Synchronously sets the python venv environment folder.
355+
///
356+
/// This is a blocking wrapper around `set_venv`. It is intended for use in
357+
/// synchronous applications.
358+
///
359+
/// * `venv_path`: Path to the venv directory.
360+
///
361+
/// **Note:** Calling this from an existing async runtime can lead to panics.
362+
pub fn set_venv_sync(&self, venv_path: &Path) -> Result<(), PyRunnerError> {
363+
let rt =
364+
tokio::runtime::Runtime::new().map_err(|e| PyRunnerError::PyError(e.to_string()))?;
365+
rt.block_on(self.set_venv(venv_path))
366+
}
242367
}
243368

244369
#[cfg(test)]
@@ -276,17 +401,18 @@ z = x + y"#;
276401
assert_eq!(z_val, Value::Number(30.into()));
277402
}
278403

404+
279405
#[tokio::test]
280406
async fn test_run_with_function() {
281407
// cargo test tests::test_run_with_function --release -- --nocapture
282-
let start_time = std::time::Instant::now();
283408
let executor = PyRunner::new();
284409
let code = r#"
285410
def add(a, b):
286411
return a + b
287412
"#;
288413

289414
executor.run(code).await.unwrap();
415+
let start_time = std::time::Instant::now();
290416
let result = executor
291417
.call_function("add", vec![5.into(), 9.into()])
292418
.await
@@ -296,6 +422,25 @@ def add(a, b):
296422
println!("test_run_with_function took: {} microseconds", duration.as_micros());
297423
}
298424

425+
#[test]
426+
fn test_sync_run_with_function() {
427+
// cargo test tests::test_run_with_function --release -- --nocapture
428+
let executor = PyRunner::new();
429+
let code = r#"
430+
def add(a, b):
431+
return a + b
432+
"#;
433+
434+
executor.run_sync(code).unwrap();
435+
let start_time = std::time::Instant::now();
436+
let result = executor
437+
.call_function_sync("add", vec![5.into(), 9.into()])
438+
.unwrap();
439+
assert_eq!(result, Value::Number(14.into()));
440+
let duration = start_time.elapsed();
441+
println!("test_run_with_function_sync took: {} microseconds", duration.as_micros());
442+
}
443+
299444
#[cfg(feature = "pyo3")]
300445
#[tokio::test]
301446
async fn test_run_with_async_function() {
@@ -319,6 +464,25 @@ async def add_and_sleep(a, b, sleep_time):
319464
assert_eq!(result2.unwrap(), Value::Number(16.into()));
320465
}
321466

467+
#[cfg(feature = "pyo3")]
468+
#[test]
469+
fn test_run_with_async_function_sync() {
470+
let executor = PyRunner::new();
471+
let code = r#"
472+
import asyncio
473+
474+
async def add(a, b):
475+
await asyncio.sleep(0.1)
476+
return a + b
477+
"#;
478+
479+
executor.run_sync(code).unwrap();
480+
let result = executor
481+
.call_async_function_sync("add", vec![5.into(), 9.into()])
482+
.unwrap();
483+
assert_eq!(result, Value::Number(14.into()));
484+
}
485+
322486
#[tokio::test]
323487
async fn test_concurrent_calls() {
324488
let executor = PyRunner::new();

0 commit comments

Comments
 (0)