inline_python/
lib.rs

1//! Inline Python code directly in your Rust code.
2//!
3//! # Example
4//!
5//! ```
6//! use inline_python::python;
7//!
8//! let who = "world";
9//! let n = 5;
10//! python! {
11//!     for i in range('n):
12//!         print(i, "Hello", 'who)
13//!     print("Goodbye")
14//! }
15//! ```
16//!
17//! # How to use
18//!
19//! Use the `python!{..}` macro to write Python code directly in your Rust code.
20//!
21//! ## Using Rust variables
22//!
23//! To reference Rust variables, use `'var`, as shown in the example above.
24//! `var` needs to implement [`pyo3::IntoPyObject`].
25//!
26//! ## Re-using a Python context
27//!
28//! It is possible to create a [`Context`] object ahead of time and use it for running the Python code.
29//! The context can be re-used for multiple invocations to share global variables across macro calls.
30//!
31//! ```
32//! # use inline_python::{Context, python};
33//! let c = Context::new();
34//!
35//! c.run(python! {
36//!   foo = 5
37//! });
38//!
39//! c.run(python! {
40//!   assert foo == 5
41//! });
42//! ```
43//!
44//! As a shortcut, you can assign a `python!{}` invocation directly to a
45//! variable of type `Context` to create a new context and run the Python code
46//! in it.
47//!
48//! ```
49//! # use inline_python::{Context, python};
50//! let c: Context = python! {
51//!   foo = 5
52//! };
53//!
54//! c.run(python! {
55//!   assert foo == 5
56//! });
57//! ```
58//!
59//! ## Getting information back
60//!
61//! A [`Context`] object could also be used to pass information back to Rust,
62//! as you can retrieve the global Python variables from the context through
63//! [`Context::get`].
64//!
65//! ```
66//! # use inline_python::{Context, python};
67//! let c: Context = python! {
68//!   foo = 5
69//! };
70//!
71//! assert_eq!(c.get::<i32>("foo"), 5);
72//! ```
73//!
74//! ## Syntax issues
75//!
76//! Since the Rust tokenizer will tokenize the Python code, some valid Python
77//! code is rejected. The main things to remember are:
78//!
79//! - Use double quoted strings (`""`) instead of single quoted strings (`''`).
80//!
81//!   (Single quoted strings only work if they contain a single character, since
82//!   in Rust, `'a'` is a character literal.)
83//!
84//! - Use `//`-comments instead of `#`-comments.
85//!
86//!   (If you use `#` comments, the Rust tokenizer will try to tokenize your
87//!   comment, and complain if your comment doesn't tokenize properly.)
88//!
89//! - Write `f ""` instead of `f""`.
90//!
91//!   (String literals with prefixes, like `f""`, are reserved in Rust for
92//!   future use. You can write `f ""` instead, which is automatically
93//!   converted back to to `f""`.)
94//!
95//! Other minor things that don't work are:
96//!
97//! - The `//` and `//=` operators are unusable, as they start a comment.
98//!
99//!   Workaround: you can write `##` instead, which is automatically converted
100//!   to `//`.
101//!
102//! - Certain escape codes in string literals.
103//!   (Specifically: `\a`, `\b`, `\f`, `\v`, `\N{..}`, `\123` (octal escape
104//!   codes), `\u`, and `\U`.)
105//!
106//!   These, however, are accepted just fine: `\\`, `\n`, `\t`, `\r`, `\xAB`
107//!   (hex escape codes), and `\0`.
108//!
109//! - Raw string literals with escaped double quotes. (E.g. `r"...\"..."`.)
110//!
111//! - Triple-quoted byte- and raw-strings with content that would not be valid
112//!   as a regular string. And the same for raw-byte and raw-format strings.
113//!   (E.g. `b"""\xFF"""`, `r"""\z"""`, `fr"\z"`, `br"\xFF"`.)
114//!
115//! Everything else should work fine.
116
117use pyo3::{Bound, Python, types::PyDict};
118
119mod context;
120mod run;
121
122pub use self::context::Context;
123pub use pyo3;
124
125/// A block of Python code within your Rust code.
126///
127/// This macro can be used in three different ways:
128///
129///  1. By itself as a statement.
130///     In this case, the Python code is executed directly.
131///
132///  2. By assigning it to a [`Context`].
133///     In this case, the Python code is executed directly, and the context
134///     (the global variables) are available for re-use by other Python code
135///     or inspection by Rust code.
136///
137///  3. By passing it as an argument to a function taking a `PythonBlock`, such
138///     as [`Context::run`].
139///
140/// See [the crate's module level documentation](index.html) for examples.
141pub use inline_python_macros::python;
142
143// `python!{..}` expands to `python_impl!{b"bytecode" var1 var2 …}`,
144// which then expands to a call to `FromInlinePython::from_python_macro`.
145#[macro_export]
146#[doc(hidden)]
147macro_rules! _python_block {
148    ($bytecode:literal $($var:ident)*) => {
149        $crate::FromInlinePython::from_python_macro(
150            // The compiled python bytecode:
151            $bytecode,
152            // The closure that puts all the captured variables in the 'globals' dictionary:
153            |globals| {
154                $(
155                    $crate::pyo3::prelude::PyDictMethods::set_item(
156                        globals, concat!("_RUST_", stringify!($var)), $var
157                    ).expect("python");
158                )*
159            },
160            // The closure that is used to throw panics with the right location:
161            |e| ::std::panic::panic_any(e),
162        )
163    }
164}
165
166#[doc(hidden)]
167pub trait FromInlinePython<F: FnOnce(&Bound<PyDict>)> {
168    /// The `python!{}` macro expands to a call to this function.
169    fn from_python_macro(bytecode: &'static [u8], set_vars: F, panic: fn(String) -> !) -> Self;
170}
171
172/// Converting a `python!{}` block to `()` will run the Python code.
173///
174/// This happens when `python!{}` is used as a statement by itself.
175impl<F: FnOnce(&Bound<PyDict>)> FromInlinePython<F> for () {
176    #[track_caller]
177    fn from_python_macro(bytecode: &'static [u8], set_vars: F, panic: fn(String) -> !) {
178        let _: Context = FromInlinePython::from_python_macro(bytecode, set_vars, panic);
179    }
180}
181
182/// Assigning a `python!{}` block to a `Context` will run the Python code and capture the resulting context.
183impl<F: FnOnce(&Bound<PyDict>)> FromInlinePython<F> for Context {
184    #[track_caller]
185    fn from_python_macro(bytecode: &'static [u8], set_vars: F, panic: fn(String) -> !) -> Self {
186        Python::with_gil(|py| {
187            let context = Context::new_with_gil(py);
188            context.run_with_gil(
189                py,
190                PythonBlock {
191                    bytecode,
192                    set_vars,
193                    panic,
194                },
195            );
196            context
197        })
198    }
199}
200
201/// Using a `python!{}` block as a `PythonBlock` object will not do anything yet.
202#[cfg(not(doc))]
203impl<F: FnOnce(&Bound<PyDict>)> FromInlinePython<F> for PythonBlock<F> {
204    fn from_python_macro(bytecode: &'static [u8], set_vars: F, panic: fn(String) -> !) -> Self {
205        Self {
206            bytecode,
207            set_vars,
208            panic,
209        }
210    }
211}
212
213/// Represents a `python!{}` block.
214#[cfg(not(doc))]
215pub struct PythonBlock<F> {
216    bytecode: &'static [u8],
217    set_vars: F,
218    panic: fn(String) -> !,
219}
220
221/// In the documentation, we just show `PythonBlock` in
222/// `Context::run`'s signature, without any generic arguments.
223#[cfg(doc)]
224#[doc(hidden)]
225pub struct PythonBlock;