Skip to content

Commit 1a6a51f

Browse files
committed
wip: Prevent Writing to 'var allow for #var
1 parent 7604f78 commit 1a6a51f

File tree

6 files changed

+67
-10
lines changed

6 files changed

+67
-10
lines changed

examples/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ path = "matplotlib.rs"
1515
name = "rust-fn"
1616
path = "rust-fn.rs"
1717

18+
[[example]]
19+
name = "var-write"
20+
path = "var-write.rs"
21+
1822
[dependencies]
1923
inline-python = { path = ".." }
2024
pyo3 = "0.11.0"

examples/var-write.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use inline_python::python;
2+
3+
fn main() {
4+
let mut x = 42;
5+
python! {
6+
#x = #x - 6
7+
assert 'x == 42
8+
assert #x == 36
9+
10+
}
11+
assert_eq!(x, 36);
12+
}

macros/src/embed_python.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,11 @@ impl EmbedPython {
7878
} else {
7979
unreachable!()
8080
};
81-
let name_str = format!("_RUST_{}", name);
81+
let name_str = name.to_string();
82+
self.python.push_str("_RUST_IMMUT_VARS['");
8283
self.python.push_str(&name_str);
83-
self.loc.column += name_str.chars().count() - 6 + 1;
84+
self.python.push_str("']");
85+
self.loc.column += name_str.chars().count() + 1;
8486
self.variables.entry(name_str).or_insert(name);
8587
} else if x.as_char() == '#' && x.spacing() == Spacing::Joint {
8688
// Convert '##' to '//', because otherwise it's

macros/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ fn python_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
5151
Ok(quote! {
5252
::inline_python::FromInlinePython::from_python_macro(
5353
#bytecode,
54-
|globals| {
54+
|rust_vars| {
5555
#(
56-
globals
56+
rust_vars
5757
.set_item(#varname, #var)
5858
.expect("Unable to convert variable to Python");
5959
)*

src/context.rs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,35 @@ impl Context {
6666
}
6767

6868
fn try_new(py: Python) -> PyResult<Self> {
69-
Ok(Self {
70-
globals: py.import("__main__")?.dict().copy()?.into(),
71-
})
69+
let rust_vars = PyDict::new(py);
70+
let globals = PyDict::new(py);
71+
let ctx = Self { globals: globals.into() };
72+
ctx.run_with_gil(
73+
py,
74+
inline_python_macros::python! {
75+
class RustVars:
76+
__slots__ = ("_dict", "_mutable")
77+
def __init__(self, mutable:bool):
78+
self._dict = dict()
79+
self._mutable = mutable
80+
81+
def __setitem__(self, key, value):
82+
if self._mutable:
83+
self._dict[key] = value
84+
else:
85+
raise TypeError("You are trying to write to "+'\''+key+" which is read-only. Try #"+key)
86+
87+
def __getitem__(self, key):
88+
return self._dict[key]
89+
90+
_RUST_MUT_VARS = RustVars(True)
91+
_RUST_IMMUT_VARS = RustVars(False)
92+
93+
del RustVars
94+
from builtins import *
95+
},
96+
);
97+
Ok(ctx)
7298
}
7399

74100
/// Get the globals as dictionary.
@@ -192,7 +218,16 @@ impl Context {
192218
///
193219
/// This function panics if the Python code fails.
194220
pub fn run_with_gil<F: FnOnce(&PyDict)>(&self, py: Python<'_>, code: PythonBlock<F>) {
195-
(code.set_variables)(self.globals(py));
221+
let immut_vars = self
222+
.globals(py)
223+
.get_item("_RUST_IMMUT_VARS")
224+
.unwrap()
225+
.getattr("_dict")
226+
.unwrap()
227+
.cast_as::<PyDict>()
228+
.unwrap();
229+
230+
(code.set_variables)(&immut_vars);
196231
match run_python_code(py, self, code.bytecode) {
197232
Ok(_) => (),
198233
Err(e) => {
@@ -201,4 +236,8 @@ impl Context {
201236
}
202237
}
203238
}
239+
240+
pub fn try_run<F: FnOnce(&PyDict)>(&self, code: PythonBlock<F>) -> Result<(), pyo3::PyErr> {
241+
todo!();
242+
}
204243
}

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
//! ## Using Rust variables
2222
//!
2323
//! To reference Rust variables, use `'var`, as shown in the example above.
24-
//! `var` needs to implement [`pyo3::ToPyObject`].
24+
//! `var` needs to implement [`pyo3::ToPyObject`] and can only be read!
2525
//!
2626
//! ## Re-using a Python context
2727
//!
@@ -108,6 +108,7 @@
108108
//!
109109
//! Everything else should work fine.
110110
111+
pub use inline_python_macros::python;
111112
use pyo3::{types::PyDict, Python};
112113

113114
mod context;
@@ -132,7 +133,6 @@ pub use pyo3;
132133
/// as [`Context::run`].
133134
///
134135
/// See [the crate's module level documentation](index.html) for examples.
135-
pub use inline_python_macros::python;
136136
137137
#[doc(hidden)]
138138
pub trait FromInlinePython<F: FnOnce(&PyDict)> {

0 commit comments

Comments
 (0)