Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose py_run! macro #512

Merged
merged 4 commits into from
Jun 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]);
kngwyu marked this conversation as resolved.
Show resolved Hide resolved
/// 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
/// "#);
/// ```
kngwyu marked this conversation as resolved.
Show resolved Hide resolved
///
/// **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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be generally useful, I think this should not panic on exceptions, or reflect this in the name?

}};
}

/// 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