1 | use crate::err::{PyErr, PyResult}; |
2 | use crate::ffi_ptr_ext::FfiPtrExt; |
3 | use crate::impl_::callback::IntoPyCallbackOutput; |
4 | use crate::py_result_ext::PyResultExt; |
5 | use crate::pyclass::PyClass; |
6 | use crate::types::{ |
7 | any::PyAnyMethods, list::PyListMethods, PyAny, PyCFunction, PyDict, PyList, PyString, |
8 | }; |
9 | use crate::{ |
10 | exceptions, ffi, Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt, Py, PyObject, |
11 | Python, |
12 | }; |
13 | use std::ffi::{CStr, CString}; |
14 | #[cfg (all(not(Py_LIMITED_API), Py_GIL_DISABLED))] |
15 | use std::os::raw::c_int; |
16 | use std::str; |
17 | |
18 | /// Represents a Python [`module`][1] object. |
19 | /// |
20 | /// Values of this type are accessed via PyO3's smart pointers, e.g. as |
21 | /// [`Py<PyModule>`][crate::Py] or [`Bound<'py, PyModule>`][Bound]. |
22 | /// |
23 | /// For APIs available on `module` objects, see the [`PyModuleMethods`] trait which is implemented for |
24 | /// [`Bound<'py, PyModule>`][Bound]. |
25 | /// |
26 | /// As with all other Python objects, modules are first class citizens. |
27 | /// This means they can be passed to or returned from functions, |
28 | /// created dynamically, assigned to variables and so forth. |
29 | /// |
30 | /// [1]: https://docs.python.org/3/tutorial/modules.html |
31 | #[repr (transparent)] |
32 | pub struct PyModule(PyAny); |
33 | |
34 | pyobject_native_type_core!(PyModule, pyobject_native_static_type_object!(ffi::PyModule_Type), #checkfunction=ffi::PyModule_Check); |
35 | |
36 | impl PyModule { |
37 | /// Creates a new module object with the `__name__` attribute set to `name`. |
38 | /// |
39 | /// # Examples |
40 | /// |
41 | /// ``` rust |
42 | /// use pyo3::prelude::*; |
43 | /// |
44 | /// # fn main() -> PyResult<()> { |
45 | /// Python::with_gil(|py| -> PyResult<()> { |
46 | /// let module = PyModule::new(py, "my_module" )?; |
47 | /// |
48 | /// assert_eq!(module.name()?, "my_module" ); |
49 | /// Ok(()) |
50 | /// })?; |
51 | /// # Ok(())} |
52 | /// ``` |
53 | pub fn new<'py>(py: Python<'py>, name: &str) -> PyResult<Bound<'py, PyModule>> { |
54 | let name = PyString::new(py, name); |
55 | unsafe { |
56 | ffi::PyModule_NewObject(name.as_ptr()) |
57 | .assume_owned_or_err(py) |
58 | .downcast_into_unchecked() |
59 | } |
60 | } |
61 | |
62 | /// Deprecated name for [`PyModule::new`]. |
63 | #[deprecated (since = "0.23.0" , note = "renamed to `PyModule::new`" )] |
64 | #[inline ] |
65 | pub fn new_bound<'py>(py: Python<'py>, name: &str) -> PyResult<Bound<'py, PyModule>> { |
66 | Self::new(py, name) |
67 | } |
68 | |
69 | /// Imports the Python module with the specified name. |
70 | /// |
71 | /// # Examples |
72 | /// |
73 | /// ```no_run |
74 | /// # fn main() { |
75 | /// use pyo3::prelude::*; |
76 | /// |
77 | /// Python::with_gil(|py| { |
78 | /// let module = PyModule::import(py, "antigravity" ).expect("No flying for you." ); |
79 | /// }); |
80 | /// # } |
81 | /// ``` |
82 | /// |
83 | /// This is equivalent to the following Python expression: |
84 | /// ```python |
85 | /// import antigravity |
86 | /// ``` |
87 | /// |
88 | /// If you want to import a class, you can store a reference to it with |
89 | /// [`GILOnceCell::import`][crate::sync::GILOnceCell#method.import]. |
90 | pub fn import<'py, N>(py: Python<'py>, name: N) -> PyResult<Bound<'py, PyModule>> |
91 | where |
92 | N: IntoPyObject<'py, Target = PyString>, |
93 | { |
94 | let name = name.into_pyobject_or_pyerr(py)?; |
95 | unsafe { |
96 | ffi::PyImport_Import(name.as_ptr()) |
97 | .assume_owned_or_err(py) |
98 | .downcast_into_unchecked() |
99 | } |
100 | } |
101 | |
102 | /// Deprecated name for [`PyModule::import`]. |
103 | #[deprecated (since = "0.23.0" , note = "renamed to `PyModule::import`" )] |
104 | #[allow (deprecated)] |
105 | #[inline ] |
106 | pub fn import_bound<N>(py: Python<'_>, name: N) -> PyResult<Bound<'_, PyModule>> |
107 | where |
108 | N: crate::IntoPy<Py<PyString>>, |
109 | { |
110 | Self::import(py, name.into_py(py)) |
111 | } |
112 | |
113 | /// Creates and loads a module named `module_name`, |
114 | /// containing the Python code passed to `code` |
115 | /// and pretending to live at `file_name`. |
116 | /// |
117 | /// <div class="information"> |
118 | /// <div class="tooltip compile_fail" style="">⚠ ️</div> |
119 | /// </div><div class="example-wrap" style="display:inline-block"><pre class="compile_fail" style="white-space:normal;font:inherit;"> |
120 | // |
121 | /// <strong>Warning</strong>: This will compile and execute code. <strong>Never</strong> pass untrusted code to this function! |
122 | /// |
123 | /// </pre></div> |
124 | /// |
125 | /// # Errors |
126 | /// |
127 | /// Returns `PyErr` if: |
128 | /// - `code` is not syntactically correct Python. |
129 | /// - Any Python exceptions are raised while initializing the module. |
130 | /// - Any of the arguments cannot be converted to [`CString`]s. |
131 | /// |
132 | /// # Example: bundle in a file at compile time with [`include_str!`][std::include_str]: |
133 | /// |
134 | /// ```rust |
135 | /// use pyo3::prelude::*; |
136 | /// use pyo3::ffi::c_str; |
137 | /// |
138 | /// # fn main() -> PyResult<()> { |
139 | /// // This path is resolved relative to this file. |
140 | /// let code = c_str!(include_str!("../../assets/script.py" )); |
141 | /// |
142 | /// Python::with_gil(|py| -> PyResult<()> { |
143 | /// PyModule::from_code(py, code, c_str!("example.py" ), c_str!("example" ))?; |
144 | /// Ok(()) |
145 | /// })?; |
146 | /// # Ok(()) |
147 | /// # } |
148 | /// ``` |
149 | /// |
150 | /// # Example: Load a file at runtime with [`std::fs::read_to_string`]. |
151 | /// |
152 | /// ```rust |
153 | /// use pyo3::prelude::*; |
154 | /// use pyo3::ffi::c_str; |
155 | /// use std::ffi::CString; |
156 | /// |
157 | /// # fn main() -> PyResult<()> { |
158 | /// // This path is resolved by however the platform resolves paths, |
159 | /// // which also makes this less portable. Consider using `include_str` |
160 | /// // if you just want to bundle a script with your module. |
161 | /// let code = std::fs::read_to_string("assets/script.py" )?; |
162 | /// |
163 | /// Python::with_gil(|py| -> PyResult<()> { |
164 | /// PyModule::from_code(py, CString::new(code)?.as_c_str(), c_str!("example.py" ), c_str!("example" ))?; |
165 | /// Ok(()) |
166 | /// })?; |
167 | /// Ok(()) |
168 | /// # } |
169 | /// ``` |
170 | pub fn from_code<'py>( |
171 | py: Python<'py>, |
172 | code: &CStr, |
173 | file_name: &CStr, |
174 | module_name: &CStr, |
175 | ) -> PyResult<Bound<'py, PyModule>> { |
176 | unsafe { |
177 | let code = ffi::Py_CompileString(code.as_ptr(), file_name.as_ptr(), ffi::Py_file_input) |
178 | .assume_owned_or_err(py)?; |
179 | |
180 | ffi::PyImport_ExecCodeModuleEx(module_name.as_ptr(), code.as_ptr(), file_name.as_ptr()) |
181 | .assume_owned_or_err(py) |
182 | .downcast_into() |
183 | } |
184 | } |
185 | |
186 | /// Deprecated name for [`PyModule::from_code`]. |
187 | #[deprecated (since = "0.23.0" , note = "renamed to `PyModule::from_code`" )] |
188 | #[inline ] |
189 | pub fn from_code_bound<'py>( |
190 | py: Python<'py>, |
191 | code: &str, |
192 | file_name: &str, |
193 | module_name: &str, |
194 | ) -> PyResult<Bound<'py, PyModule>> { |
195 | let data = CString::new(code)?; |
196 | let filename = CString::new(file_name)?; |
197 | let module = CString::new(module_name)?; |
198 | |
199 | Self::from_code(py, data.as_c_str(), filename.as_c_str(), module.as_c_str()) |
200 | } |
201 | } |
202 | |
203 | /// Implementation of functionality for [`PyModule`]. |
204 | /// |
205 | /// These methods are defined for the `Bound<'py, PyModule>` smart pointer, so to use method call |
206 | /// syntax these methods are separated into a trait, because stable Rust does not yet support |
207 | /// `arbitrary_self_types`. |
208 | #[doc (alias = "PyModule" )] |
209 | pub trait PyModuleMethods<'py>: crate::sealed::Sealed { |
210 | /// Returns the module's `__dict__` attribute, which contains the module's symbol table. |
211 | fn dict(&self) -> Bound<'py, PyDict>; |
212 | |
213 | /// Returns the index (the `__all__` attribute) of the module, |
214 | /// creating one if needed. |
215 | /// |
216 | /// `__all__` declares the items that will be imported with `from my_module import *`. |
217 | fn index(&self) -> PyResult<Bound<'py, PyList>>; |
218 | |
219 | /// Returns the name (the `__name__` attribute) of the module. |
220 | /// |
221 | /// May fail if the module does not have a `__name__` attribute. |
222 | fn name(&self) -> PyResult<Bound<'py, PyString>>; |
223 | |
224 | /// Returns the filename (the `__file__` attribute) of the module. |
225 | /// |
226 | /// May fail if the module does not have a `__file__` attribute. |
227 | fn filename(&self) -> PyResult<Bound<'py, PyString>>; |
228 | |
229 | /// Adds an attribute to the module. |
230 | /// |
231 | /// For adding classes, functions or modules, prefer to use [`PyModuleMethods::add_class`], |
232 | /// [`PyModuleMethods::add_function`] or [`PyModuleMethods::add_submodule`] instead, |
233 | /// respectively. |
234 | /// |
235 | /// # Examples |
236 | /// |
237 | /// ```rust |
238 | /// use pyo3::prelude::*; |
239 | /// |
240 | /// #[pymodule] |
241 | /// fn my_module(module: &Bound<'_, PyModule>) -> PyResult<()> { |
242 | /// module.add("c" , 299_792_458)?; |
243 | /// Ok(()) |
244 | /// } |
245 | /// ``` |
246 | /// |
247 | /// Python code can then do the following: |
248 | /// |
249 | /// ```python |
250 | /// from my_module import c |
251 | /// |
252 | /// print("c is", c) |
253 | /// ``` |
254 | /// |
255 | /// This will result in the following output: |
256 | /// |
257 | /// ```text |
258 | /// c is 299792458 |
259 | /// ``` |
260 | fn add<N, V>(&self, name: N, value: V) -> PyResult<()> |
261 | where |
262 | N: IntoPyObject<'py, Target = PyString>, |
263 | V: IntoPyObject<'py>; |
264 | |
265 | /// Adds a new class to the module. |
266 | /// |
267 | /// Notice that this method does not take an argument. |
268 | /// Instead, this method is *generic*, and requires us to use the |
269 | /// "turbofish" syntax to specify the class we want to add. |
270 | /// |
271 | /// # Examples |
272 | /// |
273 | /// ```rust |
274 | /// use pyo3::prelude::*; |
275 | /// |
276 | /// #[pyclass] |
277 | /// struct Foo { /* fields omitted */ } |
278 | /// |
279 | /// #[pymodule] |
280 | /// fn my_module(module: &Bound<'_, PyModule>) -> PyResult<()> { |
281 | /// module.add_class::<Foo>()?; |
282 | /// Ok(()) |
283 | /// } |
284 | /// ``` |
285 | /// |
286 | /// Python code can see this class as such: |
287 | /// ```python |
288 | /// from my_module import Foo |
289 | /// |
290 | /// print("Foo is", Foo) |
291 | /// ``` |
292 | /// |
293 | /// This will result in the following output: |
294 | /// ```text |
295 | /// Foo is <class 'builtins.Foo'> |
296 | /// ``` |
297 | /// |
298 | /// Note that as we haven't defined a [constructor][1], Python code can't actually |
299 | /// make an *instance* of `Foo` (or *get* one for that matter, as we haven't exported |
300 | /// anything that can return instances of `Foo`). |
301 | /// |
302 | #[doc = concat!("[1]: https://pyo3.rs/v" , env!("CARGO_PKG_VERSION" ), "/class.html#constructor" )] |
303 | fn add_class<T>(&self) -> PyResult<()> |
304 | where |
305 | T: PyClass; |
306 | |
307 | /// Adds a function or a (sub)module to a module, using the functions name as name. |
308 | /// |
309 | /// Prefer to use [`PyModuleMethods::add_function`] and/or [`PyModuleMethods::add_submodule`] |
310 | /// instead. |
311 | fn add_wrapped<T>(&self, wrapper: &impl Fn(Python<'py>) -> T) -> PyResult<()> |
312 | where |
313 | T: IntoPyCallbackOutput<'py, PyObject>; |
314 | |
315 | /// Adds a submodule to a module. |
316 | /// |
317 | /// This is especially useful for creating module hierarchies. |
318 | /// |
319 | /// Note that this doesn't define a *package*, so this won't allow Python code |
320 | /// to directly import submodules by using |
321 | /// <span style="white-space: pre">`from my_module import submodule`</span>. |
322 | /// For more information, see [#759][1] and [#1517][2]. |
323 | /// |
324 | /// # Examples |
325 | /// |
326 | /// ```rust |
327 | /// use pyo3::prelude::*; |
328 | /// |
329 | /// #[pymodule] |
330 | /// fn my_module(py: Python<'_>, module: &Bound<'_, PyModule>) -> PyResult<()> { |
331 | /// let submodule = PyModule::new(py, "submodule" )?; |
332 | /// submodule.add("super_useful_constant" , "important" )?; |
333 | /// |
334 | /// module.add_submodule(&submodule)?; |
335 | /// Ok(()) |
336 | /// } |
337 | /// ``` |
338 | /// |
339 | /// Python code can then do the following: |
340 | /// |
341 | /// ```python |
342 | /// import my_module |
343 | /// |
344 | /// print("super_useful_constant is", my_module.submodule.super_useful_constant) |
345 | /// ``` |
346 | /// |
347 | /// This will result in the following output: |
348 | /// |
349 | /// ```text |
350 | /// super_useful_constant is important |
351 | /// ``` |
352 | /// |
353 | /// [1]: https://github.com/PyO3/pyo3/issues/759 |
354 | /// [2]: https://github.com/PyO3/pyo3/issues/1517#issuecomment-808664021 |
355 | fn add_submodule(&self, module: &Bound<'_, PyModule>) -> PyResult<()>; |
356 | |
357 | /// Add a function to a module. |
358 | /// |
359 | /// Note that this also requires the [`wrap_pyfunction!`][2] macro |
360 | /// to wrap a function annotated with [`#[pyfunction]`][1]. |
361 | /// |
362 | /// ```rust |
363 | /// use pyo3::prelude::*; |
364 | /// |
365 | /// #[pyfunction] |
366 | /// fn say_hello() { |
367 | /// println!("Hello world!" ) |
368 | /// } |
369 | /// #[pymodule] |
370 | /// fn my_module(module: &Bound<'_, PyModule>) -> PyResult<()> { |
371 | /// module.add_function(wrap_pyfunction!(say_hello, module)?) |
372 | /// } |
373 | /// ``` |
374 | /// |
375 | /// Python code can then do the following: |
376 | /// |
377 | /// ```python |
378 | /// from my_module import say_hello |
379 | /// |
380 | /// say_hello() |
381 | /// ``` |
382 | /// |
383 | /// This will result in the following output: |
384 | /// |
385 | /// ```text |
386 | /// Hello world! |
387 | /// ``` |
388 | /// |
389 | /// [1]: crate::prelude::pyfunction |
390 | /// [2]: crate::wrap_pyfunction |
391 | fn add_function(&self, fun: Bound<'_, PyCFunction>) -> PyResult<()>; |
392 | |
393 | /// Declare whether or not this module supports running with the GIL disabled |
394 | /// |
395 | /// If the module does not rely on the GIL for thread safety, you can pass |
396 | /// `false` to this function to indicate the module does not rely on the GIL |
397 | /// for thread-safety. |
398 | /// |
399 | /// This function sets the [`Py_MOD_GIL` |
400 | /// slot](https://docs.python.org/3/c-api/module.html#c.Py_mod_gil) on the |
401 | /// module object. The default is `Py_MOD_GIL_USED`, so passing `true` to |
402 | /// this function is a no-op unless you have already set `Py_MOD_GIL` to |
403 | /// `Py_MOD_GIL_NOT_USED` elsewhere. |
404 | /// |
405 | /// # Examples |
406 | /// |
407 | /// ```rust |
408 | /// use pyo3::prelude::*; |
409 | /// |
410 | /// #[pymodule(gil_used = false)] |
411 | /// fn my_module(py: Python<'_>, module: &Bound<'_, PyModule>) -> PyResult<()> { |
412 | /// let submodule = PyModule::new(py, "submodule" )?; |
413 | /// submodule.gil_used(false)?; |
414 | /// module.add_submodule(&submodule)?; |
415 | /// Ok(()) |
416 | /// } |
417 | /// ``` |
418 | /// |
419 | /// The resulting module will not print a `RuntimeWarning` and re-enable the |
420 | /// GIL when Python imports it on the free-threaded build, since all module |
421 | /// objects defined in the extension have `Py_MOD_GIL` set to |
422 | /// `Py_MOD_GIL_NOT_USED`. |
423 | /// |
424 | /// This is a no-op on the GIL-enabled build. |
425 | fn gil_used(&self, gil_used: bool) -> PyResult<()>; |
426 | } |
427 | |
428 | impl<'py> PyModuleMethods<'py> for Bound<'py, PyModule> { |
429 | fn dict(&self) -> Bound<'py, PyDict> { |
430 | unsafe { |
431 | // PyModule_GetDict returns borrowed ptr; must make owned for safety (see #890). |
432 | ffi::PyModule_GetDict(self.as_ptr()) |
433 | .assume_borrowed(self.py()) |
434 | .to_owned() |
435 | .downcast_into_unchecked() |
436 | } |
437 | } |
438 | |
439 | fn index(&self) -> PyResult<Bound<'py, PyList>> { |
440 | let __all__ = __all__(self.py()); |
441 | match self.getattr(__all__) { |
442 | Ok(idx) => idx.downcast_into().map_err(PyErr::from), |
443 | Err(err) => { |
444 | if err.is_instance_of::<exceptions::PyAttributeError>(self.py()) { |
445 | let l = PyList::empty(self.py()); |
446 | self.setattr(__all__, &l)?; |
447 | Ok(l) |
448 | } else { |
449 | Err(err) |
450 | } |
451 | } |
452 | } |
453 | } |
454 | |
455 | fn name(&self) -> PyResult<Bound<'py, PyString>> { |
456 | #[cfg (not(PyPy))] |
457 | { |
458 | unsafe { |
459 | ffi::PyModule_GetNameObject(self.as_ptr()) |
460 | .assume_owned_or_err(self.py()) |
461 | .downcast_into_unchecked() |
462 | } |
463 | } |
464 | |
465 | #[cfg (PyPy)] |
466 | { |
467 | self.dict() |
468 | .get_item("__name__" ) |
469 | .map_err(|_| exceptions::PyAttributeError::new_err("__name__" ))? |
470 | .downcast_into() |
471 | .map_err(PyErr::from) |
472 | } |
473 | } |
474 | |
475 | fn filename(&self) -> PyResult<Bound<'py, PyString>> { |
476 | #[cfg (not(PyPy))] |
477 | unsafe { |
478 | ffi::PyModule_GetFilenameObject(self.as_ptr()) |
479 | .assume_owned_or_err(self.py()) |
480 | .downcast_into_unchecked() |
481 | } |
482 | |
483 | #[cfg (PyPy)] |
484 | { |
485 | self.dict() |
486 | .get_item("__file__" ) |
487 | .map_err(|_| exceptions::PyAttributeError::new_err("__file__" ))? |
488 | .downcast_into() |
489 | .map_err(PyErr::from) |
490 | } |
491 | } |
492 | |
493 | fn add<N, V>(&self, name: N, value: V) -> PyResult<()> |
494 | where |
495 | N: IntoPyObject<'py, Target = PyString>, |
496 | V: IntoPyObject<'py>, |
497 | { |
498 | fn inner( |
499 | module: &Bound<'_, PyModule>, |
500 | name: Borrowed<'_, '_, PyString>, |
501 | value: Borrowed<'_, '_, PyAny>, |
502 | ) -> PyResult<()> { |
503 | module |
504 | .index()? |
505 | .append(name) |
506 | .expect("could not append __name__ to __all__" ); |
507 | module.setattr(name, value) |
508 | } |
509 | |
510 | let py = self.py(); |
511 | inner( |
512 | self, |
513 | name.into_pyobject_or_pyerr(py)?.as_borrowed(), |
514 | value.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), |
515 | ) |
516 | } |
517 | |
518 | fn add_class<T>(&self) -> PyResult<()> |
519 | where |
520 | T: PyClass, |
521 | { |
522 | let py = self.py(); |
523 | self.add(T::NAME, T::lazy_type_object().get_or_try_init(py)?) |
524 | } |
525 | |
526 | fn add_wrapped<T>(&self, wrapper: &impl Fn(Python<'py>) -> T) -> PyResult<()> |
527 | where |
528 | T: IntoPyCallbackOutput<'py, PyObject>, |
529 | { |
530 | fn inner(module: &Bound<'_, PyModule>, object: Bound<'_, PyAny>) -> PyResult<()> { |
531 | let name = object.getattr(__name__(module.py()))?; |
532 | module.add(name.downcast_into::<PyString>()?, object) |
533 | } |
534 | |
535 | let py = self.py(); |
536 | inner(self, wrapper(py).convert(py)?.into_bound(py)) |
537 | } |
538 | |
539 | fn add_submodule(&self, module: &Bound<'_, PyModule>) -> PyResult<()> { |
540 | let name = module.name()?; |
541 | self.add(name, module) |
542 | } |
543 | |
544 | fn add_function(&self, fun: Bound<'_, PyCFunction>) -> PyResult<()> { |
545 | let name = fun.getattr(__name__(self.py()))?; |
546 | self.add(name.downcast_into::<PyString>()?, fun) |
547 | } |
548 | |
549 | #[cfg_attr (any(Py_LIMITED_API, not(Py_GIL_DISABLED)), allow(unused_variables))] |
550 | fn gil_used(&self, gil_used: bool) -> PyResult<()> { |
551 | #[cfg (all(not(Py_LIMITED_API), Py_GIL_DISABLED))] |
552 | { |
553 | let gil_used = match gil_used { |
554 | true => ffi::Py_MOD_GIL_USED, |
555 | false => ffi::Py_MOD_GIL_NOT_USED, |
556 | }; |
557 | match unsafe { ffi::PyUnstable_Module_SetGIL(self.as_ptr(), gil_used) } { |
558 | c_int::MIN..=-1 => Err(PyErr::fetch(self.py())), |
559 | 0..=c_int::MAX => Ok(()), |
560 | } |
561 | } |
562 | #[cfg (any(Py_LIMITED_API, not(Py_GIL_DISABLED)))] |
563 | Ok(()) |
564 | } |
565 | } |
566 | |
567 | fn __all__(py: Python<'_>) -> &Bound<'_, PyString> { |
568 | intern!(py, "__all__" ) |
569 | } |
570 | |
571 | fn __name__(py: Python<'_>) -> &Bound<'_, PyString> { |
572 | intern!(py, "__name__" ) |
573 | } |
574 | |
575 | #[cfg (test)] |
576 | mod tests { |
577 | use crate::{ |
578 | types::{module::PyModuleMethods, PyModule}, |
579 | Python, |
580 | }; |
581 | |
582 | #[test ] |
583 | fn module_import_and_name() { |
584 | Python::with_gil(|py| { |
585 | let builtins = PyModule::import(py, "builtins" ).unwrap(); |
586 | assert_eq!(builtins.name().unwrap(), "builtins" ); |
587 | }) |
588 | } |
589 | |
590 | #[test ] |
591 | fn module_filename() { |
592 | use crate::types::string::PyStringMethods; |
593 | Python::with_gil(|py| { |
594 | let site = PyModule::import(py, "site" ).unwrap(); |
595 | assert!(site |
596 | .filename() |
597 | .unwrap() |
598 | .to_cow() |
599 | .unwrap() |
600 | .ends_with("site.py" )); |
601 | }) |
602 | } |
603 | } |
604 | |