Skip to content

Commit

Permalink
make GILOnceCell::get_or_try_init_type_ref public (#4542)
Browse files Browse the repository at this point in the history
* make `GILOnceCell::get_or_try_init_type_ref` public

* change `GILOnceCell::get_or_try_init_type_ref` to `GILOnceCell::import`

* update doc

Co-authored-by: Bruno Kolenbrander <[email protected]>

* update doc

Co-authored-by: Bruno Kolenbrander <[email protected]>

* update docs

---------

Co-authored-by: Bruno Kolenbrander <[email protected]>
  • Loading branch information
glevco and mejrs authored Sep 21, 2024
1 parent 3b39a83 commit 6b3f887
Show file tree
Hide file tree
Showing 12 changed files with 60 additions and 22 deletions.
17 changes: 10 additions & 7 deletions guide/src/python-from-rust/calling-existing-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ If you already have some existing Python code that you need to execute from Rust

## Want to access Python APIs? Then use `PyModule::import`.

[`PyModule::import`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.import) can
be used to get handle to a Python module from Rust. You can use this to import and use any Python
[`PyModule::import`] can be used to get handle to a Python module from Rust. You can use this to import and use any Python
module available in your environment.

```rust
Expand All @@ -24,9 +23,11 @@ fn main() -> PyResult<()> {
}
```

## Want to run just an expression? Then use `eval_bound`.
[`PyModule::import`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.import

[`Python::eval_bound`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.eval_bound) is
## Want to run just an expression? Then use `eval`.

[`Python::eval`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.eval) is
a method to execute a [Python expression](https://docs.python.org/3/reference/expressions.html)
and return the evaluated value as a `Bound<'py, PyAny>` object.

Expand All @@ -48,17 +49,19 @@ Python::with_gil(|py| {
# }
```

## Want to run statements? Then use `run_bound`.
## Want to run statements? Then use `run`.

[`Python::run_bound`] is a method to execute one or more
[`Python::run`] is a method to execute one or more
[Python statements](https://docs.python.org/3/reference/simple_stmts.html).
This method returns nothing (like any Python statement), but you can get
access to manipulated objects via the `locals` dict.

You can also use the [`py_run!`] macro, which is a shorthand for [`Python::run_bound`].
You can also use the [`py_run!`] macro, which is a shorthand for [`Python::run`].
Since [`py_run!`] panics on exceptions, we recommend you use this macro only for
quickly testing your Python extensions.

[`Python::run`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.run

```rust
use pyo3::prelude::*;
use pyo3::py_run;
Expand Down
1 change: 1 addition & 0 deletions newsfragments/4542.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Change `GILOnceCell::get_or_try_init_type_ref` to `GILOnceCell::import` and make it public API.
2 changes: 1 addition & 1 deletion src/conversions/chrono_tz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl<'py> IntoPyObject<'py> for Tz {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
static ZONE_INFO: GILOnceCell<Py<PyType>> = GILOnceCell::new();
ZONE_INFO
.get_or_try_init_type_ref(py, "zoneinfo", "ZoneInfo")
.import(py, "zoneinfo", "ZoneInfo")
.and_then(|obj| obj.call1((self.name(),)))
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/conversions/num_rational.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ use num_rational::Ratio;
static FRACTION_CLS: GILOnceCell<Py<PyType>> = GILOnceCell::new();

fn get_fraction_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
FRACTION_CLS.get_or_try_init_type_ref(py, "fractions", "Fraction")
FRACTION_CLS.import(py, "fractions", "Fraction")
}

macro_rules! rational_conversion {
Expand Down
2 changes: 1 addition & 1 deletion src/conversions/rust_decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl FromPyObject<'_> for Decimal {
static DECIMAL_CLS: GILOnceCell<Py<PyType>> = GILOnceCell::new();

fn get_decimal_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
DECIMAL_CLS.get_or_try_init_type_ref(py, "decimal", "Decimal")
DECIMAL_CLS.import(py, "decimal", "Decimal")
}

impl ToPyObject for Decimal {
Expand Down
4 changes: 2 additions & 2 deletions src/conversions/std/ipaddr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl<'py> IntoPyObject<'py> for Ipv4Addr {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
static IPV4_ADDRESS: GILOnceCell<Py<PyType>> = GILOnceCell::new();
IPV4_ADDRESS
.get_or_try_init_type_ref(py, "ipaddress", "IPv4Address")?
.import(py, "ipaddress", "IPv4Address")?
.call1((u32::from_be_bytes(self.octets()),))
}
}
Expand Down Expand Up @@ -77,7 +77,7 @@ impl<'py> IntoPyObject<'py> for Ipv6Addr {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
static IPV6_ADDRESS: GILOnceCell<Py<PyType>> = GILOnceCell::new();
IPV6_ADDRESS
.get_or_try_init_type_ref(py, "ipaddress", "IPv6Address")?
.import(py, "ipaddress", "IPv6Address")?
.call1((u128::from_be_bytes(self.octets()),))
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/conversions/std/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl<'py> IntoPyObject<'py> for Duration {
{
static TIMEDELTA: GILOnceCell<Py<PyType>> = GILOnceCell::new();
TIMEDELTA
.get_or_try_init_type_ref(py, "datetime", "timedelta")?
.import(py, "datetime", "timedelta")?
.call1((days, seconds, microseconds))
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/impl_/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl ImportedExceptionTypeObject {

pub fn get<'py>(&self, py: Python<'py>) -> &Bound<'py, PyType> {
self.imported_value
.get_or_try_init_type_ref(py, self.module, self.name)
.import(py, self.module, self.name)
.unwrap_or_else(|e| {
panic!(
"failed to import exception {}.{}: {}",
Expand Down
43 changes: 37 additions & 6 deletions src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
//!
//! [PEP 703]: https://peps.python.org/pep-703/
use crate::{
types::{any::PyAnyMethods, PyString, PyType},
Bound, Py, PyResult, Python,
types::{any::PyAnyMethods, PyString},
Bound, Py, PyResult, PyTypeCheck, Python,
};
use std::cell::UnsafeCell;

Expand Down Expand Up @@ -214,16 +214,47 @@ impl<T> GILOnceCell<Py<T>> {
}
}

impl GILOnceCell<Py<PyType>> {
/// Get a reference to the contained Python type, initializing it if needed.
impl<T> GILOnceCell<Py<T>>
where
T: PyTypeCheck,
{
/// Get a reference to the contained Python type, initializing the cell if needed.
///
/// This is a shorthand method for `get_or_init` which imports the type from Python on init.
pub(crate) fn get_or_try_init_type_ref<'py>(
///
/// # Example: Using `GILOnceCell` to store a class in a static variable.
///
/// `GILOnceCell` can be used to avoid importing a class multiple times:
/// ```
/// # use pyo3::prelude::*;
/// # use pyo3::sync::GILOnceCell;
/// # use pyo3::types::{PyDict, PyType};
/// # use pyo3::intern;
/// #
/// #[pyfunction]
/// fn create_ordered_dict<'py>(py: Python<'py>, dict: Bound<'py, PyDict>) -> PyResult<Bound<'py, PyAny>> {
/// // Even if this function is called multiple times,
/// // the `OrderedDict` class will be imported only once.
/// static ORDERED_DICT: GILOnceCell<Py<PyType>> = GILOnceCell::new();
/// ORDERED_DICT
/// .import(py, "collections", "OrderedDict")?
/// .call1((dict,))
/// }
///
/// # Python::with_gil(|py| {
/// # let dict = PyDict::new(py);
/// # dict.set_item(intern!(py, "foo"), 42).unwrap();
/// # let fun = wrap_pyfunction!(create_ordered_dict, py).unwrap();
/// # let ordered_dict = fun.call1((&dict,)).unwrap();
/// # assert!(dict.eq(ordered_dict).unwrap());
/// # });
/// ```
pub fn import<'py>(
&self,
py: Python<'py>,
module_name: &str,
attr_name: &str,
) -> PyResult<&Bound<'py, PyType>> {
) -> PyResult<&Bound<'py, T>> {
self.get_or_try_init(py, || {
let type_object = py
.import(module_name)?
Expand Down
2 changes: 1 addition & 1 deletion src/types/mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ impl<'py> PyMappingMethods<'py> for Bound<'py, PyMapping> {
fn get_mapping_abc(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
static MAPPING_ABC: GILOnceCell<Py<PyType>> = GILOnceCell::new();

MAPPING_ABC.get_or_try_init_type_ref(py, "collections.abc", "Mapping")
MAPPING_ABC.import(py, "collections.abc", "Mapping")
}

impl PyTypeCheck for PyMapping {
Expand Down
3 changes: 3 additions & 0 deletions src/types/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ impl PyModule {
/// ```python
/// import antigravity
/// ```
///
/// If you want to import a class, you can store a reference to it with
/// [`GILOnceCell::import`][crate::sync::GILOnceCell#method.import].
pub fn import<N>(py: Python<'_>, name: N) -> PyResult<Bound<'_, PyModule>>
where
N: IntoPy<Py<PyString>>,
Expand Down
2 changes: 1 addition & 1 deletion src/types/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ where
fn get_sequence_abc(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
static SEQUENCE_ABC: GILOnceCell<Py<PyType>> = GILOnceCell::new();

SEQUENCE_ABC.get_or_try_init_type_ref(py, "collections.abc", "Sequence")
SEQUENCE_ABC.import(py, "collections.abc", "Sequence")
}

impl PyTypeCheck for PySequence {
Expand Down

0 comments on commit 6b3f887

Please sign in to comment.