| 1 | //! Implementation details of `#[pymodule]` which need to be accessible from proc-macro generated code. |
| 2 | |
| 3 | use std::{cell::UnsafeCell, ffi::CStr, marker::PhantomData}; |
| 4 | |
| 5 | #[cfg (all( |
| 6 | not(any(PyPy, GraalPy)), |
| 7 | Py_3_9, |
| 8 | not(all(windows, Py_LIMITED_API, not(Py_3_10))), |
| 9 | not(target_has_atomic = "64" ), |
| 10 | ))] |
| 11 | use portable_atomic::AtomicI64; |
| 12 | #[cfg (all( |
| 13 | not(any(PyPy, GraalPy)), |
| 14 | Py_3_9, |
| 15 | not(all(windows, Py_LIMITED_API, not(Py_3_10))), |
| 16 | target_has_atomic = "64" , |
| 17 | ))] |
| 18 | use std::sync::atomic::AtomicI64; |
| 19 | use std::sync::atomic::{AtomicBool, Ordering}; |
| 20 | |
| 21 | #[cfg (not(any(PyPy, GraalPy)))] |
| 22 | use crate::exceptions::PyImportError; |
| 23 | #[cfg (all(not(Py_LIMITED_API), Py_GIL_DISABLED))] |
| 24 | use crate::PyErr; |
| 25 | use crate::{ |
| 26 | ffi, |
| 27 | impl_::pymethods::PyMethodDef, |
| 28 | sync::GILOnceCell, |
| 29 | types::{PyCFunction, PyModule, PyModuleMethods}, |
| 30 | Bound, Py, PyClass, PyResult, PyTypeInfo, Python, |
| 31 | }; |
| 32 | |
| 33 | /// `Sync` wrapper of `ffi::PyModuleDef`. |
| 34 | pub struct ModuleDef { |
| 35 | // wrapped in UnsafeCell so that Rust compiler treats this as interior mutability |
| 36 | ffi_def: UnsafeCell<ffi::PyModuleDef>, |
| 37 | initializer: ModuleInitializer, |
| 38 | /// Interpreter ID where module was initialized (not applicable on PyPy). |
| 39 | #[cfg (all( |
| 40 | not(any(PyPy, GraalPy)), |
| 41 | Py_3_9, |
| 42 | not(all(windows, Py_LIMITED_API, not(Py_3_10))) |
| 43 | ))] |
| 44 | interpreter: AtomicI64, |
| 45 | /// Initialized module object, cached to avoid reinitialization. |
| 46 | module: GILOnceCell<Py<PyModule>>, |
| 47 | /// Whether or not the module supports running without the GIL |
| 48 | gil_used: AtomicBool, |
| 49 | } |
| 50 | |
| 51 | /// Wrapper to enable initializer to be used in const fns. |
| 52 | pub struct ModuleInitializer(pub for<'py> fn(&Bound<'py, PyModule>) -> PyResult<()>); |
| 53 | |
| 54 | unsafe impl Sync for ModuleDef {} |
| 55 | |
| 56 | impl ModuleDef { |
| 57 | /// Make new module definition with given module name. |
| 58 | pub const unsafe fn new( |
| 59 | name: &'static CStr, |
| 60 | doc: &'static CStr, |
| 61 | initializer: ModuleInitializer, |
| 62 | ) -> Self { |
| 63 | #[allow (clippy::declare_interior_mutable_const)] |
| 64 | const INIT: ffi::PyModuleDef = ffi::PyModuleDef { |
| 65 | m_base: ffi::PyModuleDef_HEAD_INIT, |
| 66 | m_name: std::ptr::null(), |
| 67 | m_doc: std::ptr::null(), |
| 68 | m_size: 0, |
| 69 | m_methods: std::ptr::null_mut(), |
| 70 | m_slots: std::ptr::null_mut(), |
| 71 | m_traverse: None, |
| 72 | m_clear: None, |
| 73 | m_free: None, |
| 74 | }; |
| 75 | |
| 76 | let ffi_def = UnsafeCell::new(ffi::PyModuleDef { |
| 77 | m_name: name.as_ptr(), |
| 78 | m_doc: doc.as_ptr(), |
| 79 | ..INIT |
| 80 | }); |
| 81 | |
| 82 | ModuleDef { |
| 83 | ffi_def, |
| 84 | initializer, |
| 85 | // -1 is never expected to be a valid interpreter ID |
| 86 | #[cfg (all( |
| 87 | not(any(PyPy, GraalPy)), |
| 88 | Py_3_9, |
| 89 | not(all(windows, Py_LIMITED_API, not(Py_3_10))) |
| 90 | ))] |
| 91 | interpreter: AtomicI64::new(-1), |
| 92 | module: GILOnceCell::new(), |
| 93 | gil_used: AtomicBool::new(true), |
| 94 | } |
| 95 | } |
| 96 | /// Builds a module using user given initializer. Used for [`#[pymodule]`][crate::pymodule]. |
| 97 | #[cfg_attr (any(Py_LIMITED_API, not(Py_GIL_DISABLED)), allow(unused_variables))] |
| 98 | pub fn make_module(&'static self, py: Python<'_>, gil_used: bool) -> PyResult<Py<PyModule>> { |
| 99 | // Check the interpreter ID has not changed, since we currently have no way to guarantee |
| 100 | // that static data is not reused across interpreters. |
| 101 | // |
| 102 | // PyPy does not have subinterpreters, so no need to check interpreter ID. |
| 103 | #[cfg (not(any(PyPy, GraalPy)))] |
| 104 | { |
| 105 | // PyInterpreterState_Get is only available on 3.9 and later, but is missing |
| 106 | // from python3.dll for Windows stable API on 3.9 |
| 107 | #[cfg (all(Py_3_9, not(all(windows, Py_LIMITED_API, not(Py_3_10)))))] |
| 108 | { |
| 109 | let current_interpreter = |
| 110 | unsafe { ffi::PyInterpreterState_GetID(ffi::PyInterpreterState_Get()) }; |
| 111 | crate::err::error_on_minusone(py, current_interpreter)?; |
| 112 | if let Err(initialized_interpreter) = self.interpreter.compare_exchange( |
| 113 | -1, |
| 114 | current_interpreter, |
| 115 | Ordering::SeqCst, |
| 116 | Ordering::SeqCst, |
| 117 | ) { |
| 118 | if initialized_interpreter != current_interpreter { |
| 119 | return Err(PyImportError::new_err( |
| 120 | "PyO3 modules do not yet support subinterpreters, see https://github.com/PyO3/pyo3/issues/576" , |
| 121 | )); |
| 122 | } |
| 123 | } |
| 124 | } |
| 125 | #[cfg (not(all(Py_3_9, not(all(windows, Py_LIMITED_API, not(Py_3_10))))))] |
| 126 | { |
| 127 | // CPython before 3.9 does not have APIs to check the interpreter ID, so best that can be |
| 128 | // done to guard against subinterpreters is fail if the module is initialized twice |
| 129 | if self.module.get(py).is_some() { |
| 130 | return Err(PyImportError::new_err( |
| 131 | "PyO3 modules compiled for CPython 3.8 or older may only be initialized once per interpreter process" |
| 132 | )); |
| 133 | } |
| 134 | } |
| 135 | } |
| 136 | self.module |
| 137 | .get_or_try_init(py, || { |
| 138 | let module = unsafe { |
| 139 | Py::<PyModule>::from_owned_ptr_or_err( |
| 140 | py, |
| 141 | ffi::PyModule_Create(self.ffi_def.get()), |
| 142 | )? |
| 143 | }; |
| 144 | #[cfg (all(not(Py_LIMITED_API), Py_GIL_DISABLED))] |
| 145 | { |
| 146 | let gil_used_ptr = { |
| 147 | if gil_used { |
| 148 | ffi::Py_MOD_GIL_USED |
| 149 | } else { |
| 150 | ffi::Py_MOD_GIL_NOT_USED |
| 151 | } |
| 152 | }; |
| 153 | if unsafe { ffi::PyUnstable_Module_SetGIL(module.as_ptr(), gil_used_ptr) } < 0 { |
| 154 | return Err(PyErr::fetch(py)); |
| 155 | } |
| 156 | } |
| 157 | self.initializer.0(module.bind(py))?; |
| 158 | Ok(module) |
| 159 | }) |
| 160 | .map(|py_module| py_module.clone_ref(py)) |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | /// Trait to add an element (class, function...) to a module. |
| 165 | /// |
| 166 | /// Currently only implemented for classes. |
| 167 | pub trait PyAddToModule: crate::sealed::Sealed { |
| 168 | fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()>; |
| 169 | } |
| 170 | |
| 171 | /// For adding native types (non-pyclass) to a module. |
| 172 | pub struct AddTypeToModule<T>(PhantomData<T>); |
| 173 | |
| 174 | impl<T> AddTypeToModule<T> { |
| 175 | #[allow (clippy::new_without_default)] |
| 176 | pub const fn new() -> Self { |
| 177 | AddTypeToModule(PhantomData) |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | impl<T: PyTypeInfo> PyAddToModule for AddTypeToModule<T> { |
| 182 | fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> { |
| 183 | module.add(T::NAME, T::type_object(module.py())) |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | /// For adding a class to a module. |
| 188 | pub struct AddClassToModule<T>(PhantomData<T>); |
| 189 | |
| 190 | impl<T> AddClassToModule<T> { |
| 191 | #[allow (clippy::new_without_default)] |
| 192 | pub const fn new() -> Self { |
| 193 | AddClassToModule(PhantomData) |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | impl<T: PyClass> PyAddToModule for AddClassToModule<T> { |
| 198 | fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> { |
| 199 | module.add_class::<T>() |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | /// For adding a function to a module. |
| 204 | impl PyAddToModule for PyMethodDef { |
| 205 | fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> { |
| 206 | module.add_function(fun:PyCFunction::internal_new(module.py(), self, module:Some(module))?) |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | /// For adding a module to a module. |
| 211 | impl PyAddToModule for ModuleDef { |
| 212 | fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> { |
| 213 | module.add_submodule( |
| 214 | self.make_module(module.py(), self.gil_used.load(order:Ordering::Relaxed))? |
| 215 | .bind(module.py()), |
| 216 | ) |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | #[cfg (test)] |
| 221 | mod tests { |
| 222 | use std::{ |
| 223 | borrow::Cow, |
| 224 | ffi::CStr, |
| 225 | sync::atomic::{AtomicBool, Ordering}, |
| 226 | }; |
| 227 | |
| 228 | use crate::{ |
| 229 | ffi, |
| 230 | types::{any::PyAnyMethods, module::PyModuleMethods, PyModule}, |
| 231 | Bound, PyResult, Python, |
| 232 | }; |
| 233 | |
| 234 | use super::{ModuleDef, ModuleInitializer}; |
| 235 | |
| 236 | #[test ] |
| 237 | fn module_init() { |
| 238 | static MODULE_DEF: ModuleDef = unsafe { |
| 239 | ModuleDef::new( |
| 240 | ffi::c_str!("test_module" ), |
| 241 | ffi::c_str!("some doc" ), |
| 242 | ModuleInitializer(|m| { |
| 243 | m.add("SOME_CONSTANT" , 42)?; |
| 244 | Ok(()) |
| 245 | }), |
| 246 | ) |
| 247 | }; |
| 248 | Python::with_gil(|py| { |
| 249 | let module = MODULE_DEF.make_module(py, false).unwrap().into_bound(py); |
| 250 | assert_eq!( |
| 251 | module |
| 252 | .getattr("__name__" ) |
| 253 | .unwrap() |
| 254 | .extract::<Cow<'_, str>>() |
| 255 | .unwrap(), |
| 256 | "test_module" , |
| 257 | ); |
| 258 | assert_eq!( |
| 259 | module |
| 260 | .getattr("__doc__" ) |
| 261 | .unwrap() |
| 262 | .extract::<Cow<'_, str>>() |
| 263 | .unwrap(), |
| 264 | "some doc" , |
| 265 | ); |
| 266 | assert_eq!( |
| 267 | module |
| 268 | .getattr("SOME_CONSTANT" ) |
| 269 | .unwrap() |
| 270 | .extract::<u8>() |
| 271 | .unwrap(), |
| 272 | 42, |
| 273 | ); |
| 274 | }) |
| 275 | } |
| 276 | |
| 277 | #[test ] |
| 278 | fn module_def_new() { |
| 279 | // To get coverage for ModuleDef::new() need to create a non-static ModuleDef, however init |
| 280 | // etc require static ModuleDef, so this test needs to be separated out. |
| 281 | static NAME: &CStr = ffi::c_str!("test_module" ); |
| 282 | static DOC: &CStr = ffi::c_str!("some doc" ); |
| 283 | |
| 284 | static INIT_CALLED: AtomicBool = AtomicBool::new(false); |
| 285 | |
| 286 | #[allow (clippy::unnecessary_wraps)] |
| 287 | fn init(_: &Bound<'_, PyModule>) -> PyResult<()> { |
| 288 | INIT_CALLED.store(true, Ordering::SeqCst); |
| 289 | Ok(()) |
| 290 | } |
| 291 | |
| 292 | unsafe { |
| 293 | let module_def: ModuleDef = ModuleDef::new(NAME, DOC, ModuleInitializer(init)); |
| 294 | assert_eq!((*module_def.ffi_def.get()).m_name, NAME.as_ptr() as _); |
| 295 | assert_eq!((*module_def.ffi_def.get()).m_doc, DOC.as_ptr() as _); |
| 296 | |
| 297 | Python::with_gil(|py| { |
| 298 | module_def.initializer.0(&py.import("builtins" ).unwrap()).unwrap(); |
| 299 | assert!(INIT_CALLED.load(Ordering::SeqCst)); |
| 300 | }) |
| 301 | } |
| 302 | } |
| 303 | } |
| 304 | |