Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make GILOnceCell::get_or_try_init_type_ref public #4542

Merged
merged 5 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading