Expose py_run macro

This commit is contained in:
kngwyu 2019-06-13 18:09:17 +09:00
parent f2a7c64365
commit 0f9a3b1194
15 changed files with 103 additions and 55 deletions

View File

@ -25,11 +25,11 @@ num-traits = "0.2.6"
pyo3cls = { path = "pyo3cls", version = "=0.7.0" }
mashup = "0.1.9"
num-complex = { version = "0.2.1", optional = true }
indoc = "0.3.3"
inventory = "0.1.3"
[dev-dependencies]
assert_approx_eq = "1.1.0"
indoc = "0.3.3"
trybuild = "1.0"
[build-dependencies]

View File

@ -133,6 +133,9 @@ pub use crate::type_object::{PyObjectAlloc, PyRawObject, PyTypeInfo};
// Re-exported for wrap_function
#[doc(hidden)]
pub use mashup;
// Re-exported for py_run
#[doc(hidden)]
pub use indoc;
// Re-exported for pymethods
#[doc(hidden)]
pub use inventory;
@ -210,6 +213,96 @@ macro_rules! wrap_pymodule {
}};
}
/// A convinient macro to execute a Python code snippet, with some local variables set.
///
/// # Example
/// ```
/// use pyo3::{prelude::*, py_run, types::PyList};
/// let gil = Python::acquire_gil();
/// let py = gil.python();
/// let obj = pyo3::types::PyDict::new(py);
/// let list = PyList::new(py, &[1, 2, 3]);
/// py_run!(py, list, "assert list == [1, 2, 3]");
/// ```
///
/// You can use this macro to test pyfunctions or pyclasses quickly.
///
/// # Example
/// ```
/// use pyo3::{prelude::*, PyRawObject, py_run};
/// #[pyclass]
/// struct Time {
/// hour: u32,
/// minute: u32,
/// second: u32,
/// }
/// #[pymethods]
/// impl Time {
/// fn repl_japanese(&self) -> String {
/// format!("{}時{}分{}秒", self.hour, self.minute, self.second)
/// }
/// #[getter]
/// fn hour(&self) -> PyResult<u32> {
/// Ok(self.hour)
/// }
/// }
/// let gil = Python::acquire_gil();
/// let py = gil.python();
/// let time = PyRef::new(py, Time {hour: 8, minute: 43, second: 16}).unwrap();
/// py_run!(py, time, r#"
/// assert time.hour == 8
/// assert time.repl_japanese() == "8時43分16秒"
/// "#);
/// ```
#[macro_export]
macro_rules! py_run {
($py:expr, $($val:ident )+, $code:literal) => {{
pyo3::py_run_impl!($py, $($val)+, pyo3::indoc::indoc!($code))
}};
($py:expr, $($val:ident )+, $code:expr) => {{
pyo3::py_run_impl!($py, $($val)+, &pyo3::_indoc_runtime($code))
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_run_impl {
($py:expr, $($val:ident )+, $code:expr) => {{
use pyo3::types::IntoPyDict;
let d = [$((stringify!($val), &$val))*].into_py_dict($py);
$py.run($code, None, Some(d))
.map_err(|e| {
e.print($py);
// So when this c api function the last line called printed the error to stderr,
// the output is only written into a buffer which is never flushed because we
// panic before flushing. This is where this hack comes into place
$py.run("import sys; sys.stderr.flush()", None, None)
.unwrap();
})
.expect($code)
}};
}
/// Removes indentation from multiline strings in pyrun commands
#[doc(hidden)]
pub fn _indoc_runtime(commands: &str) -> String {
let indent;
if let Some(second) = commands.lines().nth(1) {
indent = second
.chars()
.take_while(char::is_ascii_whitespace)
.collect::<String>();
} else {
indent = "".to_string();
}
commands
.trim_end()
.replace(&("\n".to_string() + &indent), "\n")
+ "\n"
}
/// Test readme and user guide
#[doc(hidden)]
pub mod doc_test {

View File

@ -2,48 +2,10 @@
//! - Tests are run in parallel; There's still a race condition in test_owned with some other test
//! - You need to use flush=True to get any output from print
/// Removes indentation from multiline strings in pyrun commands
#[allow(unused)] // macro scoping is fooling the compiler
pub fn indoc(commands: &str) -> String {
let indent;
if let Some(second) = commands.lines().nth(1) {
indent = second
.chars()
.take_while(char::is_ascii_whitespace)
.collect::<String>();
} else {
indent = "".to_string();
}
commands
.trim_end()
.replace(&("\n".to_string() + &indent), "\n")
+ "\n"
}
#[macro_export]
macro_rules! py_run {
($py:expr, $val:expr, $code:expr) => {{
use pyo3::types::IntoPyDict;
let d = [(stringify!($val), &$val)].into_py_dict($py);
$py.run(&common::indoc($code), None, Some(d))
.map_err(|e| {
e.print($py);
// So when this c api function the last line called printed the error to stderr,
// the output is only written into a buffer which is never flushed because we
// panic before flushing. This is where this hack comes into place
$py.run("import sys; sys.stderr.flush()", None, None)
.unwrap();
})
.expect(&common::indoc($code))
}};
}
#[macro_export]
macro_rules! py_assert {
($py:expr, $val:ident, $assertion:expr) => {
py_run!($py, $val, concat!("assert ", $assertion))
pyo3::py_run!($py, $val, concat!("assert ", $assertion))
};
}

View File

@ -1,9 +1,9 @@
use pyo3::class::basic::CompareOp;
use pyo3::class::*;
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::PyAny;
#[macro_use]
mod common;
#[pyclass]

View File

@ -1,7 +1,7 @@
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::type_object::initialize_type;
#[macro_use]
mod common;
#[pyclass]

View File

@ -6,11 +6,11 @@ use pyo3::class::{
use pyo3::exceptions::{IndexError, ValueError};
use pyo3::ffi;
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::{IntoPyDict, PyAny, PyBytes, PySlice, PyType};
use pyo3::AsPyPointer;
use std::{isize, iter};
#[macro_use]
mod common;
#[pyclass]

View File

@ -3,6 +3,7 @@ use pyo3::class::PyTraverseError;
use pyo3::class::PyVisit;
use pyo3::ffi;
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::PyAny;
use pyo3::types::PyTuple;
use pyo3::AsPyPointer;
@ -11,7 +12,6 @@ use std::cell::RefCell;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
#[macro_use]
mod common;
#[pyclass(freelist = 2)]

View File

@ -1,8 +1,8 @@
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::IntoPyDict;
use std::isize;
#[macro_use]
mod common;
#[pyclass]

View File

@ -1,8 +1,8 @@
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::IntoPyDict;
use std::isize;
#[macro_use]
mod common;
#[pyclass]

View File

@ -1,8 +1,8 @@
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::{IntoPyDict, PyDict, PyList, PySet, PyString, PyTuple, PyType};
use pyo3::PyRawObject;
#[macro_use]
mod common;
#[pyclass]

View File

@ -2,7 +2,6 @@ use pyo3::prelude::*;
use pyo3::types::IntoPyDict;
#[macro_use]
mod common;
#[pyclass]

View File

@ -5,7 +5,6 @@ use pyo3::types::{PyBytes, PyString};
use pyo3::PyIterProtocol;
use std::collections::HashMap;
#[macro_use]
mod common;
/// Assumes it's a file reader or so.

View File

@ -6,9 +6,6 @@ use pyo3::types::IntoPyDict;
use pyo3::types::PyAny;
use pyo3::types::PyList;
#[macro_use]
mod common;
#[pyclass]
struct ByteSequence {
elements: Vec<u8>,

View File

@ -1,7 +1,6 @@
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};
#[macro_use]
mod common;
#[pyclass]

View File

@ -2,10 +2,9 @@ use pyo3::prelude::*;
use pyo3::type_object::initialize_type;
use pyo3::types::IntoPyDict;
use pyo3::types::{PyDict, PyTuple};
use pyo3::wrap_pyfunction;
use pyo3::{py_run, wrap_pyfunction};
use std::isize;
#[macro_use]
mod common;
#[pyclass]