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(all(Py_3_9, not(Py_LIMITED_API)))]
69 fn outer(&self) -> Option<Bound<'py, PyFrame>>;
70
71 #[cfg(any(all(Py_3_9, 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(all(Py_3_9, 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(all(Py_3_9, 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 #[cfg(Py_3_9)]
197 fn get_frame(py: Python<'_>) -> Bound<'_, PyFrame> {
198 use crate::types::PyAnyMethods as _;
199
200 let m = crate::types::PyModule::from_code(
201 py,
202 cr#"
203import sys
204CONST = "global"
205def get_frame():
206 var = 42
207 return sys._getframe()
208"#,
209 c"frame.py",
210 c"frame",
211 )
212 .unwrap();
213
214 m.getattr("get_frame")
215 .unwrap()
216 .call0()
217 .unwrap()
218 .cast_into()
219 .unwrap()
220 }
221
222 #[test]
223 fn test_frame_creation() {
224 Python::attach(|py| {
225 let frame = PyFrame::new(py, c"file.py", c"func", 42).unwrap();
226 assert_eq!(frame.line_number(), 42);
227 });
228 }
229
230 #[test]
231 #[cfg(all(Py_3_9, not(Py_LIMITED_API)))]
232 fn test_frame_outer() {
233 Python::attach(|py| {
234 use crate::types::PyAnyMethods as _;
235
236 let m = crate::types::PyModule::from_code(
237 py,
238 cr#"
239import sys
240def inner():
241 return sys._getframe()
242def outer():
243 return inner()
244"#,
245 c"outer.py",
246 c"outer",
247 )
248 .unwrap();
249
250 let frame = m
251 .getattr("outer")
252 .unwrap()
253 .call0()
254 .unwrap()
255 .cast_into()
256 .unwrap();
257
258 let back = frame.outer().unwrap();
259 let f_back = frame.getattr("f_back").unwrap();
260
261 assert_eq!(back.as_ptr(), f_back.as_ptr());
262 assert_eq!(back.line_number(), 6)
263 })
264 }
265
266 #[test]
267 #[cfg(any(all(Py_3_9, not(Py_LIMITED_API)), Py_3_10))]
268 fn test_frame_get_code() {
269 Python::attach(|py| {
270 use crate::types::PyAnyMethods as _;
271
272 let frame = get_frame(py);
273 let code = frame.code();
274 let f_code = frame.getattr("f_code").unwrap();
275
276 assert_eq!(code.as_ptr(), f_code.as_ptr());
277 })
278 }
279
280 #[test]
281 #[cfg(all(Py_3_12, not(Py_LIMITED_API)))]
282 fn test_frame_get_var() {
283 Python::attach(|py| {
284 use crate::types::PyAnyMethods as _;
285
286 let frame = get_frame(py);
287 assert_eq!(frame.var(c"var").unwrap().extract::<u32>().unwrap(), 42);
288 assert!(frame.var(c"var2").is_err());
289 })
290 }
291
292 #[test]
293 #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]
294 fn test_frame_get_builtins() {
295 Python::attach(|py| {
296 use crate::types::PyAnyMethods as _;
297
298 let frame = get_frame(py);
299 let builtins = frame.builtins();
300
301 assert_eq!(
302 builtins
303 .get_item("__name__")
304 .unwrap()
305 .extract::<&str>()
306 .unwrap(),
307 "builtins"
308 );
309 assert!(builtins.contains("len").unwrap());
310 })
311 }
312
313 #[test]
314 #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]
315 #[should_panic(expected = "`PyFrame_GetBuiltins` returns a `dict`")]
316 fn test_frame_builtins_panics_on_type_error() {
317 use crate::types::PyAnyMethods as _;
318 Python::attach(|py| -> PyResult<()> {
319 let sys = py.import("sys")?;
320 let globals = PyDict::new(py);
321 globals.set_item("getframe", sys.getattr("_getframe")?)?;
322 globals.set_item("__builtins__", py.eval(c"object()", None, None)?)?;
323 py.run(c"frame = getframe()", Some(&globals), None)?;
324 let frame: Bound<'_, PyFrame> = globals.get_item("frame")?.cast_into()?;
325
326 let _builtins = frame.builtins();
328
329 Ok(())
330 })
331 .unwrap();
332 }
333
334 #[test]
335 #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]
336 fn test_frame_get_globals() {
337 Python::attach(|py| {
338 use crate::types::PyAnyMethods as _;
339
340 let frame = get_frame(py);
341 let globals = frame.globals();
342
343 assert_eq!(
344 globals
345 .get_item("CONST")
346 .unwrap()
347 .extract::<&str>()
348 .unwrap(),
349 "global"
350 );
351 })
352 }
353
354 #[test]
355 #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]
356 fn test_frame_get_locals() {
357 Python::attach(|py| {
358 use crate::types::PyAnyMethods as _;
359
360 let frame = get_frame(py);
361 let locals = frame.locals();
362
363 assert_eq!(
364 locals.get_item("var").unwrap().extract::<u32>().unwrap(),
365 42
366 );
367 })
368 }
369}