Skip to content

Commit

Permalink
Merge pull request #512 from kngwyu/expose-py-run
Browse files Browse the repository at this point in the history
Expose py_run! macro
  • Loading branch information
kngwyu authored Jun 15, 2019
2 parents 05b0cb6 + 9073956 commit 3e69389
Show file tree
Hide file tree
Showing 16 changed files with 106 additions and 55 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added

* `module` argument to `pyclass` macro. [#499](https://github.com/PyO3/pyo3/pull/499)
* `py_run!` macro [#512](https://github.com/PyO3/pyo3/pull/512)

### Fixed

* More readable error message for generics in pyclass [#503](https://github.com/PyO3/pyo3/pull/503)

### Changed

## [0.7.0] - 2018-05-26

Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ pyo3cls = { path = "pyo3cls", version = "=0.7.0" }
mashup = "0.1.9"
num-complex = { version = "0.2.1", optional = true }
inventory = "0.1.3"
indoc = "0.3.3"
unindent = "0.1.3"

[dev-dependencies]
assert_approx_eq = "1.1.0"
indoc = "0.3.3"
trybuild = "1.0"

[build-dependencies]
Expand Down
89 changes: 89 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,18 @@ 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;
// Re-exported for the `__wrap` functions
#[doc(hidden)]
pub use libc;
// Re-exported for py_run
#[doc(hidden)]
pub use unindent;

/// Raw ffi declarations for the c interface of python
pub mod ffi;
Expand Down Expand Up @@ -210,6 +216,89 @@ macro_rules! wrap_pymodule {
}};
}

/// A convenient 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 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::*, py_run};
/// #[pyclass]
/// #[derive(Debug)]
/// 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) -> u32 {
/// self.hour
/// }
/// fn as_tuple(&self) -> (u32, u32, u32) {
/// (self.hour, self.minute, self.second)
/// }
/// }
/// let gil = Python::acquire_gil();
/// let py = gil.python();
/// let time = PyRef::new(py, Time {hour: 8, minute: 43, second: 16}).unwrap();
/// let time_as_tuple = (8, 43, 16);
/// py_run!(py, time time_as_tuple, r#"
/// assert time.hour == 8
/// assert time.repl_japanese() == "8時43分16秒"
/// assert time.as_tuple() == time_as_tuple
/// "#);
/// ```
///
/// **Note**
/// Since this macro is intended to use for testing, it **causes panic** when
/// [Python::run] returns `Err` internally.
/// If you need to handle failures, please use [Python::run] directly.
///
#[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::unindent::unindent($code))
}};
}

#[macro_export]
#[doc(hidden)]
macro_rules! py_run_impl {
($py:expr, $($val:ident)+, $code:expr) => {{
use pyo3::types::IntoPyDict;
use pyo3::ToPyObject;
let d = [$((stringify!($val), $val.to_object($py)),)+].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)
}};
}

/// Test readme and user guide
#[doc(hidden)]
pub mod doc_test {
Expand Down
40 changes: 1 addition & 39 deletions tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
};
}

Expand Down
2 changes: 1 addition & 1 deletion tests/test_arithmetics.rs
Original file line number Diff line number Diff line change
@@ -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]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_class_basics.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::type_object::initialize_type;

#[macro_use]
mod common;

#[pyclass]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_dunder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_getter_setter.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::IntoPyDict;
use std::isize;

#[macro_use]
mod common;

#[pyclass]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_inheritance.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::IntoPyDict;
use std::isize;

#[macro_use]
mod common;

#[pyclass]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_methods.rs
Original file line number Diff line number Diff line change
@@ -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]
Expand Down
1 change: 0 additions & 1 deletion tests/test_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use pyo3::prelude::*;

use pyo3::types::IntoPyDict;

#[macro_use]
mod common;

#[pyclass]
Expand Down
1 change: 0 additions & 1 deletion tests/test_pyself.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 0 additions & 3 deletions tests/test_sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
1 change: 0 additions & 1 deletion tests/test_variable_arguments.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};

#[macro_use]
mod common;

#[pyclass]
Expand Down
3 changes: 1 addition & 2 deletions tests/test_various.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit 3e69389

Please sign in to comment.