Skip to main content

rustpython_vm/stdlib/
_warnings.rs

1pub(crate) use _warnings::module_def;
2
3use crate::{Py, PyResult, VirtualMachine, builtins::PyType};
4
5pub fn warn(
6    category: &Py<PyType>,
7    message: String,
8    stack_level: usize,
9    vm: &VirtualMachine,
10) -> PyResult<()> {
11    crate::warn::warn(
12        vm.new_pyobj(message),
13        Some(category.to_owned()),
14        isize::try_from(stack_level).unwrap_or(isize::MAX),
15        None,
16        vm,
17    )
18}
19
20#[pymodule]
21mod _warnings {
22    use crate::{
23        AsObject, PyObjectRef, PyResult, VirtualMachine,
24        builtins::{PyDictRef, PyListRef, PyStrRef, PyTupleRef, PyTypeRef},
25        convert::TryFromObject,
26        function::OptionalArg,
27    };
28
29    #[pyattr]
30    fn filters(vm: &VirtualMachine) -> PyListRef {
31        vm.state.warnings.filters.clone()
32    }
33
34    #[pyattr]
35    fn _defaultaction(vm: &VirtualMachine) -> PyStrRef {
36        vm.state.warnings.default_action.clone()
37    }
38
39    #[pyattr]
40    fn _onceregistry(vm: &VirtualMachine) -> PyDictRef {
41        vm.state.warnings.once_registry.clone()
42    }
43
44    #[pyattr]
45    fn _warnings_context(vm: &VirtualMachine) -> PyObjectRef {
46        vm.state
47            .warnings
48            .context_var
49            .get_or_init(|| {
50                // Try to create a real ContextVar if _contextvars is available.
51                // During early startup it may not be importable yet, in which
52                // case we fall back to None.  This is safe because
53                // context_aware_warnings defaults to False.
54                if let Ok(contextvars) = vm.import("_contextvars", 0)
55                    && let Ok(cv_cls) = contextvars.get_attr("ContextVar", vm)
56                    && let Ok(cv) = cv_cls.call(("_warnings_context",), vm)
57                {
58                    cv
59                } else {
60                    vm.ctx.none()
61                }
62            })
63            .clone()
64    }
65
66    #[pyfunction]
67    fn _acquire_lock(vm: &VirtualMachine) {
68        vm.state.warnings.acquire_lock();
69    }
70
71    #[pyfunction]
72    fn _release_lock(vm: &VirtualMachine) -> PyResult<()> {
73        if !vm.state.warnings.release_lock() {
74            return Err(vm.new_runtime_error("cannot release un-acquired lock"));
75        }
76        Ok(())
77    }
78
79    #[pyfunction]
80    fn _filters_mutated_lock_held(vm: &VirtualMachine) {
81        vm.state.warnings.filters_mutated();
82    }
83
84    #[derive(FromArgs)]
85    struct WarnArgs {
86        #[pyarg(positional)]
87        message: PyObjectRef,
88        #[pyarg(any, optional)]
89        category: OptionalArg<PyObjectRef>,
90        #[pyarg(any, optional)]
91        stacklevel: OptionalArg<i32>,
92        #[pyarg(named, optional)]
93        source: OptionalArg<PyObjectRef>,
94        #[pyarg(named, optional)]
95        skip_file_prefixes: OptionalArg<PyTupleRef>,
96    }
97
98    /// Validate and resolve the category argument, matching get_category() in C.
99    fn get_category(
100        message: &PyObjectRef,
101        category: Option<PyObjectRef>,
102        vm: &VirtualMachine,
103    ) -> PyResult<Option<PyTypeRef>> {
104        let cat_obj = match category {
105            Some(c) if !vm.is_none(&c) => c,
106            _ => {
107                if message.fast_isinstance(vm.ctx.exceptions.warning) {
108                    return Ok(Some(message.class().to_owned()));
109                } else {
110                    return Ok(None); // will default to UserWarning in warn_explicit
111                }
112            }
113        };
114
115        let cat = PyTypeRef::try_from_object(vm, cat_obj.clone()).map_err(|_| {
116            vm.new_type_error(format!(
117                "category must be a Warning subclass, not '{}'",
118                cat_obj.class().name()
119            ))
120        })?;
121
122        if !cat.fast_issubclass(vm.ctx.exceptions.warning) {
123            return Err(vm.new_type_error(format!(
124                "category must be a Warning subclass, not '{}'",
125                cat.class().name()
126            )));
127        }
128
129        Ok(Some(cat))
130    }
131
132    #[pyfunction]
133    fn warn(args: WarnArgs, vm: &VirtualMachine) -> PyResult<()> {
134        let level = args.stacklevel.unwrap_or(1) as isize;
135
136        let category = get_category(&args.message, args.category.into_option(), vm)?;
137
138        // Validate skip_file_prefixes: each element must be a str
139        let skip_prefixes = args.skip_file_prefixes.into_option();
140        if let Some(ref prefixes) = skip_prefixes {
141            for item in prefixes.iter() {
142                if !item.class().is(vm.ctx.types.str_type) {
143                    return Err(vm.new_type_error("skip_file_prefixes must be a tuple of strs"));
144                }
145            }
146        }
147
148        crate::warn::warn_with_skip(
149            args.message,
150            category,
151            level,
152            args.source.into_option(),
153            skip_prefixes,
154            vm,
155        )
156    }
157
158    #[derive(FromArgs)]
159    struct WarnExplicitArgs {
160        #[pyarg(positional)]
161        message: PyObjectRef,
162        #[pyarg(positional)]
163        category: PyObjectRef,
164        #[pyarg(positional)]
165        filename: PyStrRef,
166        #[pyarg(positional)]
167        lineno: usize,
168        #[pyarg(any, optional)]
169        module: OptionalArg<PyObjectRef>,
170        #[pyarg(any, optional)]
171        registry: OptionalArg<PyObjectRef>,
172        #[pyarg(any, optional)]
173        module_globals: OptionalArg<PyObjectRef>,
174        #[pyarg(named, optional)]
175        source: OptionalArg<PyObjectRef>,
176    }
177
178    #[pyfunction]
179    fn warn_explicit(args: WarnExplicitArgs, vm: &VirtualMachine) -> PyResult<()> {
180        let registry = args.registry.into_option().unwrap_or_else(|| vm.ctx.none());
181
182        let module = args.module.into_option();
183
184        // Validate module_globals: must be None or a dict
185        if let Some(ref mg) = args.module_globals.into_option()
186            && !vm.is_none(mg)
187            && !mg.class().is(vm.ctx.types.dict_type)
188        {
189            return Err(vm.new_type_error("module_globals must be a dict"));
190        }
191
192        let category = if vm.is_none(&args.category) {
193            None
194        } else {
195            Some(
196                PyTypeRef::try_from_object(vm, args.category)
197                    .map_err(|_| vm.new_type_error("category must be a Warning subclass"))?,
198            )
199        };
200
201        crate::warn::warn_explicit(
202            category,
203            args.message,
204            args.filename,
205            args.lineno,
206            module,
207            registry,
208            None, // source_line
209            args.source.into_option(),
210            vm,
211        )
212    }
213}