From 76510bdd0e11b0877927c0511d57b8dfc94c6932 Mon Sep 17 00:00:00 2001 From: konstin Date: Wed, 13 Jun 2018 18:02:45 +0200 Subject: [PATCH] Refactoring This is actually a failed bugfix attempt, but still useful --- Cargo.toml | 7 +++ examples/word-count-cls/src/lib.rs | 5 +-- guide/src/class.md | 4 +- pyo3-derive-backend/src/py_class.rs | 5 --- tests/common.rs | 16 ++++++- ...t_underscore_methods.rs => test_dunder.rs} | 43 +++++++++++++++++-- tests/test_various.rs | 28 ------------ 7 files changed, 66 insertions(+), 42 deletions(-) rename tests/{test_underscore_methods.rs => test_dunder.rs} (90%) diff --git a/Cargo.toml b/Cargo.toml index f01ab080..a85c23a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,3 +45,10 @@ python3 = [] # It tells the linker to keep the python symbols unresolved, # so that the module can also be used with statically linked python interpreters. extension-module = [] + +[workspace] +members = [ + "pyo3cls", + "pyo3-derive-backend", + "examples/*", +] \ No newline at end of file diff --git a/examples/word-count-cls/src/lib.rs b/examples/word-count-cls/src/lib.rs index e7d79f7f..1ba68ecd 100644 --- a/examples/word-count-cls/src/lib.rs +++ b/examples/word-count-cls/src/lib.rs @@ -1,7 +1,6 @@ // Source adopted from // https://github.com/tildeio/helix-website/blob/master/crates/word_count/src/lib.rs #![feature(proc_macro, specialization)] -#![feature(const_fn, const_align_of, const_size_of, const_ptr_null, const_ptr_null_mut)] extern crate pyo3; extern crate rayon; @@ -16,7 +15,7 @@ use pyo3::py::methods as pymethods; use pyo3::py::class as pyclass; use pyo3::py::modinit as pymodinit; -#[pyclass] +#[pyclass(dict)] struct WordCounter { path: String, token: PyToken, @@ -27,7 +26,7 @@ impl WordCounter { #[new] fn __new__(obj: &PyRawObject, path: String) -> PyResult<()> { - obj.init(|t| WordCounter {path: path, token: t}) + obj.init(|t| WordCounter {path, token: t}) } fn search(&self, py: Python, search: String) -> PyResult { diff --git a/guide/src/class.md b/guide/src/class.md index 01ba9f8e..8d28b2c8 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -47,7 +47,7 @@ are generated only if struct contains `PyToken` attribute. TODO - continue -## class macro +## The `#[class]` macro Python class generation is powered by [Procedural Macros](https://doc.rust-lang.org/book/first-edition/procedural-macros.html). To define python custom class, rust struct needs to be annotated with `#[class]` attribute. @@ -65,7 +65,7 @@ python object that can be collector `PyGCProtocol` trait has to be implemented. * `base=BaseType` - use custom base class. BaseType is type which is implements `PyTypeInfo` trait. * `subclass` - adds subclass support so that Python classes can inherit from this class -* `dict` - adds `__dict__` support, the instances of this type have a dictionary containing instance variables +* `dict` - adds `__dict__` support, the instances of this type have a dictionary containing instance variables. ## Constructor diff --git a/pyo3-derive-backend/src/py_class.rs b/pyo3-derive-backend/src/py_class.rs index 89625781..1062980a 100644 --- a/pyo3-derive-backend/src/py_class.rs +++ b/pyo3-derive-backend/src/py_class.rs @@ -420,11 +420,6 @@ fn parse_attribute( let mut flags = vec![syn::Expr::Lit(parse_quote!{0})]; let mut base: syn::TypePath = parse_quote!{_pyo3::PyObjectRef}; - // https://github.com/rust-lang/rust/pull/50120 removed the parantheses from - // the attr TokenStream, so we need to re-add them manually - // Old nightly (like 2018-04-05): ( name=CustomName ) - // New nightly (like 2018-04-28): name=CustomName - for expr in args.iter() { match expr { diff --git a/tests/common.rs b/tests/common.rs index 74787a46..8bc1035f 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,9 +1,23 @@ +/// Removes indentation from multiline strings in pyrun commands +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::(); + } else { + indent = "".to_string(); + } + + commands.trim_right().replace(&("\n".to_string() + &indent), "\n") + "\n" +} + #[macro_export] macro_rules! py_run { ($py:expr, $val:ident, $code:expr) => {{ let d = PyDict::new($py); d.set_item(stringify!($val), &$val).unwrap(); - $py.run($code, None, Some(d)).map_err(|e| e.print($py)).expect($code); + $py.run(&common::indoc($code), None, Some(d)) + .map_err(|e| e.print($py)) + .expect(&common::indoc($code)); }} } diff --git a/tests/test_underscore_methods.rs b/tests/test_dunder.rs similarity index 90% rename from tests/test_underscore_methods.rs rename to tests/test_dunder.rs index e93d7a95..3952c845 100644 --- a/tests/test_underscore_methods.rs +++ b/tests/test_dunder.rs @@ -371,16 +371,16 @@ fn context_manager() { let py = gil.python(); let c = py.init_mut(|t| ContextManager{exit_called: false, token: t}).unwrap(); - py_run!(py, c, "with c as x:\n assert x == 42"); + py_run!(py, c, "with c as x: assert x == 42"); assert!(c.exit_called); c.exit_called = false; - py_run!(py, c, "with c as x:\n raise ValueError"); + py_run!(py, c, "with c as x: raise ValueError"); assert!(c.exit_called); c.exit_called = false; py_expect_exception!( - py, c, "with c as x:\n raise NotImplementedError", NotImplementedError); + py, c, "with c as x: raise NotImplementedError", NotImplementedError); assert!(c.exit_called); } @@ -434,3 +434,40 @@ fn test_cls_impl() { py.run("assert ob[1] == 'int'", None, Some(d)).unwrap(); py.run("assert ob[100:200:1] == 'slice'", None, Some(d)).unwrap(); } + +#[pyclass(dict)] +struct DunderDictSupport { + token: PyToken, +} + +#[test] +fn dunder_dict_support() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let inst = Py::new_ref(py, |t| DunderDictSupport{token: t}).unwrap(); + py_run!(py, inst, r#" + inst.a = 1 + assert inst.a == 1 + "#); + + py_run!(py, inst, r#" + import copy + inst2 = copy.deepcopy(inst) + inst2.a = 2 + assert inst.a == 1 + "#); +} + +#[pyclass(weakref, dict)] +struct WeakRefDunderDictSupport { + token: PyToken, +} + +#[test] +fn weakref_dunder_dict_support() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let inst = Py::new_ref(py, |t| WeakRefDunderDictSupport{token: t}).unwrap(); + py_run!(py, inst, "import weakref; assert weakref.ref(inst)() is inst; inst.a = 1; assert inst.a == 1"); +} + diff --git a/tests/test_various.rs b/tests/test_various.rs index 0539d084..f49bd917 100644 --- a/tests/test_various.rs +++ b/tests/test_various.rs @@ -11,34 +11,6 @@ use pyo3::py::methods as pymethods; #[macro_use] mod common; - -#[pyclass(dict)] -struct DunderDictSupport { - token: PyToken, -} - -#[test] -fn dunder_dict_support() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let inst = Py::new_ref(py, |t| DunderDictSupport{token: t}).unwrap(); - py_run!(py, inst, "inst.a = 1; assert inst.a == 1"); -} - -#[pyclass(weakref, dict)] -struct WeakRefDunderDictSupport { - token: PyToken, -} - -#[test] -fn weakref_dunder_dict_support() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let inst = Py::new_ref(py, |t| WeakRefDunderDictSupport{token: t}).unwrap(); - py_run!(py, inst, "import weakref; assert weakref.ref(inst)() is inst; inst.a = 1; assert inst.a == 1"); -} - - #[pyclass] struct MutRefArg { n: i32,