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, PyObject, 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.to_owned()
32    }
33
34    #[pyattr]
35    fn _defaultaction(vm: &VirtualMachine) -> PyStrRef {
36        vm.state.warnings.default_action.to_owned()
37    }
38
39    #[pyattr]
40    fn _onceregistry(vm: &VirtualMachine) -> PyDictRef {
41        vm.state.warnings.once_registry.to_owned()
42    }
43
44    #[pyattr]
45    fn _warnings_context(vm: &VirtualMachine) -> PyObjectRef {
46        if let Some(ctx) = vm.state.warnings.context_var.get() {
47            return ctx.clone();
48        }
49        // _warnings is initialized before _contextvars may be importable.
50        // Retry until ContextVar can be created; do not cache a None fallback.
51        let created = vm
52            .import("_contextvars", 0)
53            .ok()
54            .and_then(|m| m.get_attr("ContextVar", vm).ok())
55            .and_then(|cv_cls| cv_cls.call(("_warnings_context",), vm).ok());
56        match created {
57            Some(cv) => match vm.state.warnings.context_var.set(cv.clone()) {
58                Ok(()) => cv,
59                Err(_) => vm.state.warnings.context_var.get().cloned().unwrap_or(cv),
60            },
61            None => vm.ctx.none(),
62        }
63    }
64
65    #[pyfunction]
66    fn _acquire_lock(vm: &VirtualMachine) {
67        vm.state.warnings.acquire_lock();
68    }
69
70    #[pyfunction]
71    fn _release_lock(vm: &VirtualMachine) -> PyResult<()> {
72        if !vm.state.warnings.release_lock() {
73            return Err(vm.new_runtime_error("cannot release un-acquired lock"));
74        }
75        Ok(())
76    }
77
78    #[pyfunction]
79    fn _filters_mutated_lock_held(vm: &VirtualMachine) {
80        vm.state.warnings.filters_mutated();
81    }
82
83    #[derive(FromArgs)]
84    struct WarnArgs {
85        #[pyarg(any)]
86        message: PyObjectRef,
87        #[pyarg(any, optional)]
88        category: OptionalArg<PyObjectRef>,
89        #[pyarg(any, default = 1)]
90        stacklevel: i32,
91        #[pyarg(any, optional)]
92        source: OptionalArg<PyObjectRef>,
93        #[pyarg(named, optional)]
94        skip_file_prefixes: OptionalArg<PyTupleRef>,
95    }
96
97    /// Validate and resolve the category argument, matching get_category() in C.
98    fn get_category(
99        message: &PyObject,
100        category: Option<PyObjectRef>,
101        vm: &VirtualMachine,
102    ) -> PyResult<Option<PyTypeRef>> {
103        let cat_obj = match category {
104            Some(c) if !vm.is_none(&c) => c,
105            _ => {
106                return Ok(if message.fast_isinstance(vm.ctx.exceptions.warning) {
107                    Some(message.class().to_owned())
108                } else {
109                    None // will default to UserWarning in warn_explicit
110                });
111            }
112        };
113
114        let cat = PyTypeRef::try_from_object(vm, cat_obj.clone()).map_err(|_| {
115            vm.new_type_error(format!(
116                "category must be a Warning subclass, not '{}'",
117                cat_obj.class().name()
118            ))
119        })?;
120
121        if !cat.fast_issubclass(vm.ctx.exceptions.warning) {
122            return Err(vm.new_type_error(format!(
123                "category must be a Warning subclass, not '{}'",
124                cat.class().name()
125            )));
126        }
127
128        Ok(Some(cat))
129    }
130
131    #[pyfunction]
132    fn warn(args: WarnArgs, vm: &VirtualMachine) -> PyResult<()> {
133        let level = args.stacklevel as isize;
134
135        let category = get_category(&args.message, args.category.into_option(), vm)?;
136
137        // Validate skip_file_prefixes: each element must be a str
138        let skip_prefixes = args.skip_file_prefixes.into_option();
139        if let Some(ref prefixes) = skip_prefixes {
140            for item in prefixes.as_slice() {
141                if !item.class().is(vm.ctx.types.str_type) {
142                    return Err(vm.new_type_error("skip_file_prefixes must be a tuple of strs"));
143                }
144            }
145        }
146
147        crate::warn::warn_with_skip(
148            args.message,
149            category,
150            level,
151            args.source.into_option(),
152            skip_prefixes.as_deref(),
153            vm,
154        )
155    }
156
157    #[derive(FromArgs)]
158    struct WarnExplicitArgs {
159        #[pyarg(any)]
160        message: PyObjectRef,
161        #[pyarg(any)]
162        category: PyObjectRef,
163        #[pyarg(any)]
164        filename: PyStrRef,
165        #[pyarg(any)]
166        lineno: usize,
167        #[pyarg(any, optional)]
168        module: OptionalArg<PyObjectRef>,
169        #[pyarg(any, optional)]
170        registry: OptionalArg<PyObjectRef>,
171        #[pyarg(any, optional)]
172        module_globals: OptionalArg<PyObjectRef>,
173        #[pyarg(any, optional)]
174        source: OptionalArg<PyObjectRef>,
175    }
176
177    #[pyfunction]
178    fn warn_explicit(args: WarnExplicitArgs, vm: &VirtualMachine) -> PyResult<()> {
179        let registry = args.registry.into_option().unwrap_or_else(|| vm.ctx.none());
180
181        let module = args.module.into_option();
182
183        let source_line = if let Some(mg) = args.module_globals.into_option() {
184            if vm.is_none(&mg) {
185                None
186            } else if !mg.class().is(vm.ctx.types.dict_type) {
187                return Err(vm.new_type_error(format!(
188                    "module_globals must be a dict, not '{}'",
189                    mg.class().name()
190                )));
191            } else {
192                crate::warn::get_source_line(&mg, args.lineno, vm)?
193            }
194        } else {
195            None
196        };
197
198        let category = if vm.is_none(&args.category) {
199            None
200        } else {
201            Some(
202                PyTypeRef::try_from_object(vm, args.category)
203                    .map_err(|_| vm.new_type_error("category must be a Warning subclass"))?,
204            )
205        };
206
207        crate::warn::warn_explicit(
208            category,
209            args.message,
210            args.filename,
211            args.lineno,
212            module,
213            registry,
214            source_line,
215            args.source.into_option(),
216            vm,
217        )
218    }
219}