1#![deny(clippy::undocumented_unsafe_blocks)]
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::sealed::Sealed;
4use crate::types::{PyCode, PyDict};
5use crate::PyAny;
6use crate::{ffi, Bound, PyResult, Python};
7use core::ffi::CStr;
8use pyo3_ffi::PyObject;
9
10#[repr(transparent)]
15pub struct PyFrame(PyAny);
16
17pyobject_native_type_core!(
18 PyFrame,
19 pyobject_native_static_type_object!(ffi::PyFrame_Type),
20 "types",
21 "FrameType",
22 #checkfunction=ffi::PyFrame_Check
23);
24
25impl PyFrame {
26 pub fn new<'py>(
28 py: Python<'py>,
29 file_name: &CStr,
30 func_name: &CStr,
31 line_number: i32,
32 ) -> PyResult<Bound<'py, PyFrame>> {
33 let state = unsafe { ffi::compat::PyThreadState_GetUnchecked() };
35 let code = PyCode::empty(py, file_name, func_name, line_number);
36 let globals = PyDict::new(py);
37 let locals = PyDict::new(py);
38
39 unsafe {
44 Ok(ffi::PyFrame_New(
45 state,
46 code.as_ptr().cast(),
47 globals.as_ptr(),
48 locals.as_ptr(),
49 )
50 .cast::<PyObject>()
51 .assume_owned_or_err(py)?
52 .cast_into_unchecked::<PyFrame>())
53 }
54 }
55}
56
57#[doc(alias = "PyFrame")]
63pub trait PyFrameMethods<'py>: Sealed {
64 fn line_number(&self) -> i32;
66
67 #[cfg(not(Py_LIMITED_API))]
69 fn outer(&self) -> Option<Bound<'py, PyFrame>>;
70
71 #[cfg(any(not(Py_LIMITED_API), Py_3_10))]
73 fn code(&self) -> Bound<'py, PyCode>;
74
75 #[cfg(all(Py_3_12, not(Py_LIMITED_API)))]
77 fn var(&self, name: &CStr) -> PyResult<Bound<'py, PyAny>>;
78
79 #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]
81 fn builtins(&self) -> Bound<'py, PyDict>;
82
83 #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]
85 fn globals(&self) -> Bound<'py, PyDict>;
86
87 #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]
89 fn locals(&self) -> Bound<'py, PyAny>;
90}
91
92impl<'py> PyFrameMethods<'py> for Bound<'py, PyFrame> {
93 fn line_number(&self) -> i32 {
94 unsafe { ffi::PyFrame_GetLineNumber(self.as_ptr().cast()) }
98 }
99
100 #[cfg(not(Py_LIMITED_API))]
101 fn outer(&self) -> Option<Bound<'py, PyFrame>> {
102 unsafe {
109 ffi::PyFrame_GetBack(self.as_ptr().cast())
110 .cast::<ffi::PyObject>()
111 .assume_owned_or_opt(self.py())
112 .map(|obj| obj.cast_into_unchecked())
113 }
114 }
115
116 #[cfg(any(not(Py_LIMITED_API), Py_3_10))]
117 fn code(&self) -> Bound<'py, PyCode> {
118 unsafe {
125 ffi::PyFrame_GetCode(self.as_ptr().cast())
126 .cast::<ffi::PyObject>()
127 .assume_owned_unchecked(self.py())
128 .cast_into_unchecked()
129 }
130 }
131
132 #[cfg(all(Py_3_12, not(Py_LIMITED_API)))]
133 fn var(&self, name: &CStr) -> PyResult<Bound<'py, PyAny>> {
134 unsafe {
140 ffi::PyFrame_GetVarString(self.as_ptr().cast(), name.as_ptr().cast_mut())
141 .assume_owned_or_err(self.py())
142 }
143 }
144
145 #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]
146 fn builtins(&self) -> Bound<'py, PyDict> {
147 unsafe {
153 ffi::PyFrame_GetBuiltins(self.as_ptr().cast())
154 .assume_owned_unchecked(self.py())
155 .cast_into()
156 .expect("`PyFrame_GetBuiltins` returns a `dict`")
163 }
164 }
165
166 #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]
167 fn globals(&self) -> Bound<'py, PyDict> {
168 unsafe {
175 ffi::PyFrame_GetGlobals(self.as_ptr().cast())
176 .assume_owned_unchecked(self.py())
177 .cast_into_unchecked()
178 }
179 }
180
181 #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]
182 fn locals(&self) -> Bound<'py, PyAny> {
183 unsafe { ffi::PyFrame_GetLocals(self.as_ptr().cast()).assume_owned_unchecked(self.py()) }
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 fn get_frame(py: Python<'_>) -> Bound<'_, PyFrame> {
197 use crate::types::PyAnyMethods as _;
198
199 let m = crate::types::PyModule::from_code(
200 py,
201 cr#"
202import sys
203CONST = "global"
204def get_frame():
205 var = 42
206 return sys._getframe()
207"#,
208 c"frame.py",
209 c"frame",
210 )
211 .unwrap();
212
213 m.getattr("get_frame")
214 .unwrap()
215 .call0()
216 .unwrap()
217 .cast_into()
218 .unwrap()
219 }
220
221 #[test]
222 fn test_frame_creation() {
223 Python::attach(|py| {
224 let frame = PyFrame::new(py, c"file.py", c"func", 42).unwrap();
225 assert_eq!(frame.line_number(), 42);
226 });
227 }
228
229 #[test]
230 #[cfg(not(Py_LIMITED_API))]
231 fn test_frame_outer() {
232 Python::attach(|py| {
233 use crate::types::PyAnyMethods as _;
234
235 let m = crate::types::PyModule::from_code(
236 py,
237 cr#"
238import sys
239def inner():
240 return sys._getframe()
241def outer():
242 return inner()
243"#,
244 c"outer.py",
245 c"outer",
246 )
247 .unwrap();
248
249 let frame = m
250 .getattr("outer")
251 .unwrap()
252 .call0()
253 .unwrap()
254 .cast_into()
255 .unwrap();
256
257 let back = frame.outer().unwrap();
258 let f_back = frame.getattr("f_back").unwrap();
259
260 assert_eq!(back.as_ptr(), f_back.as_ptr());
261 assert_eq!(back.line_number(), 6)
262 })
263 }
264
265 #[test]
266 #[cfg(any(not(Py_LIMITED_API), Py_3_10))]
267 fn test_frame_get_code() {
268 Python::attach(|py| {
269 use crate::types::PyAnyMethods as _;
270
271 let frame = get_frame(py);
272 let code = frame.code();
273 let f_code = frame.getattr("f_code").unwrap();
274
275 assert_eq!(code.as_ptr(), f_code.as_ptr());
276 })
277 }
278
279 #[test]
280 #[cfg(all(Py_3_12, not(Py_LIMITED_API)))]
281 fn test_frame_get_var() {
282 Python::attach(|py| {
283 use crate::types::PyAnyMethods as _;
284
285 let frame = get_frame(py);
286 assert_eq!(frame.var(c"var").unwrap().extract::<u32>().unwrap(), 42);
287 assert!(frame.var(c"var2").is_err());
288 })
289 }
290
291 #[test]
292 #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]
293 fn test_frame_get_builtins() {
294 Python::attach(|py| {
295 use crate::types::PyAnyMethods as _;
296
297 let frame = get_frame(py);
298 let builtins = frame.builtins();
299
300 assert_eq!(
301 builtins
302 .get_item("__name__")
303 .unwrap()
304 .extract::<&str>()
305 .unwrap(),
306 "builtins"
307 );
308 assert!(builtins.contains("len").unwrap());
309 })
310 }
311
312 #[test]
313 #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]
314 #[should_panic(expected = "`PyFrame_GetBuiltins` returns a `dict`")]
315 fn test_frame_builtins_panics_on_type_error() {
316 use crate::types::PyAnyMethods as _;
317 Python::attach(|py| -> PyResult<()> {
318 let sys = py.import("sys")?;
319 let globals = PyDict::new(py);
320 globals.set_item("getframe", sys.getattr("_getframe")?)?;
321 globals.set_item("__builtins__", py.eval(c"object()", None, None)?)?;
322 py.run(c"frame = getframe()", Some(&globals), None)?;
323 let frame: Bound<'_, PyFrame> = globals.get_item("frame")?.cast_into()?;
324
325 let _builtins = frame.builtins();
327
328 Ok(())
329 })
330 .unwrap();
331 }
332
333 #[test]
334 #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]
335 fn test_frame_get_globals() {
336 Python::attach(|py| {
337 use crate::types::PyAnyMethods as _;
338
339 let frame = get_frame(py);
340 let globals = frame.globals();
341
342 assert_eq!(
343 globals
344 .get_item("CONST")
345 .unwrap()
346 .extract::<&str>()
347 .unwrap(),
348 "global"
349 );
350 })
351 }
352
353 #[test]
354 #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]
355 fn test_frame_get_locals() {
356 Python::attach(|py| {
357 use crate::types::PyAnyMethods as _;
358
359 let frame = get_frame(py);
360 let locals = frame.locals();
361
362 assert_eq!(
363 locals.get_item("var").unwrap().extract::<u32>().unwrap(),
364 42
365 );
366 })
367 }
368}