1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use super::PyAnyMethods as _;
use super::PyDict;
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::py_result_ext::PyResultExt;
#[cfg(any(Py_LIMITED_API, PyPy))]
use crate::sync::PyOnceLock;
#[cfg(any(Py_LIMITED_API, PyPy))]
use crate::types::{PyType, PyTypeMethods};
#[cfg(any(Py_LIMITED_API, PyPy))]
use crate::Py;
use crate::{ffi, Bound, PyAny, PyErr, PyResult, Python};
use std::ffi::CStr;
/// Represents a Python code object.
///
/// Values of this type are accessed via PyO3's smart pointers, e.g. as
/// [`Py<PyCode>`][crate::Py] or [`Bound<'py, PyCode>`][crate::Bound].
#[repr(transparent)]
pub struct PyCode(PyAny);
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
pyobject_native_type_core!(
PyCode,
pyobject_native_static_type_object!(ffi::PyCode_Type),
"types",
"CodeType",
#checkfunction=ffi::PyCode_Check
);
#[cfg(any(Py_LIMITED_API, PyPy))]
pyobject_native_type_core!(
PyCode,
|py| {
static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
TYPE.import(py, "types", "CodeType").unwrap().as_type_ptr()
},
"types",
"CodeType"
);
/// Compilation mode of [`PyCode::compile`]
pub enum PyCodeInput {
/// Python grammar for isolated expressions
Eval,
/// Python grammar for sequences of statements as read from a file
File,
}
impl PyCode {
/// Compiles code in the given context.
///
/// `input` decides whether `code` is treated as
/// - [`PyCodeInput::Eval`]: an isolated expression
/// - [`PyCodeInput::File`]: a sequence of statements
pub fn compile<'py>(
py: Python<'py>,
code: &CStr,
filename: &CStr,
input: PyCodeInput,
) -> PyResult<Bound<'py, PyCode>> {
let start = match input {
PyCodeInput::Eval => ffi::Py_eval_input,
PyCodeInput::File => ffi::Py_file_input,
};
unsafe {
ffi::Py_CompileString(code.as_ptr(), filename.as_ptr(), start)
.assume_owned_or_err(py)
.cast_into_unchecked()
}
}
}
/// Implementation of functionality for [`PyCode`].
///
/// These methods are defined for the `Bound<'py, PyCode>` smart pointer, so to use method call
/// syntax these methods are separated into a trait, because stable Rust does not yet support
/// `arbitrary_self_types`.
pub trait PyCodeMethods<'py> {
/// Runs code object.
///
/// If `globals` is `None`, it defaults to Python module `__main__`.
/// If `locals` is `None`, it defaults to the value of `globals`.
fn run(
&self,
globals: Option<&Bound<'py, PyDict>>,
locals: Option<&Bound<'py, PyDict>>,
) -> PyResult<Bound<'py, PyAny>>;
}
impl<'py> PyCodeMethods<'py> for Bound<'py, PyCode> {
fn run(
&self,
globals: Option<&Bound<'py, PyDict>>,
locals: Option<&Bound<'py, PyDict>>,
) -> PyResult<Bound<'py, PyAny>> {
let mptr = unsafe {
ffi::compat::PyImport_AddModuleRef(c"__main__".as_ptr())
.assume_owned_or_err(self.py())?
};
let attr = mptr.getattr(crate::intern!(self.py(), "__dict__"))?;
let globals = match globals {
Some(globals) => globals,
None => attr.cast::<PyDict>()?,
};
let locals = locals.unwrap_or(globals);
// If `globals` don't provide `__builtins__`, most of the code will fail if Python
// version is <3.10. That's probably not what user intended, so insert `__builtins__`
// for them.
//
// See also:
// - https://github.com/python/cpython/pull/24564 (the same fix in CPython 3.10)
// - https://github.com/PyO3/pyo3/issues/3370
let builtins_s = crate::intern!(self.py(), "__builtins__");
let has_builtins = globals.contains(builtins_s)?;
if !has_builtins {
crate::sync::critical_section::with_critical_section(globals, || {
// check if another thread set __builtins__ while this thread was blocked on the critical section
let has_builtins = globals.contains(builtins_s)?;
if !has_builtins {
// Inherit current builtins.
let builtins = unsafe { ffi::PyEval_GetBuiltins() };
// `PyDict_SetItem` doesn't take ownership of `builtins`, but `PyEval_GetBuiltins`
// seems to return a borrowed reference, so no leak here.
if unsafe {
ffi::PyDict_SetItem(globals.as_ptr(), builtins_s.as_ptr(), builtins)
} == -1
{
return Err(PyErr::fetch(self.py()));
}
}
Ok(())
})?;
}
unsafe {
ffi::PyEval_EvalCode(self.as_ptr(), globals.as_ptr(), locals.as_ptr())
.assume_owned_or_err(self.py())
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_type_object() {
use crate::types::PyTypeMethods;
use crate::{PyTypeInfo, Python};
Python::attach(|py| {
assert_eq!(super::PyCode::type_object(py).name().unwrap(), "code");
})
}
}