Add `Python::with_gil`
This commit is contained in:
parent
cfa5b2e013
commit
4020e4d0c8
|
@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
## [Unreleased]
|
||||
### Added
|
||||
- Add FFI definitions `Py_FinalizeEx`, `PyOS_getsig`, `PyOS_setsig`. [#1021](https://github.com/PyO3/pyo3/pull/1021)
|
||||
- Add `Python::with_gil` for executing a closure with the Python GIL. [#1037](https://github.com/PyO3/pyo3/pull/1037)
|
||||
|
||||
### Changed
|
||||
- Correct FFI definitions `Py_SetProgramName` and `Py_SetPythonHome` to take `*const` argument instead of `*mut`. [#1021](https://github.com/PyO3/pyo3/pull/1021)
|
||||
|
|
12
README.md
12
README.md
|
@ -108,12 +108,12 @@ use pyo3::prelude::*;
|
|||
use pyo3::types::IntoPyDict;
|
||||
|
||||
fn main() -> Result<(), ()> {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
main_(py).map_err(|e| {
|
||||
// We can't display Python exceptions via std::fmt::Display,
|
||||
// so print the error here manually.
|
||||
e.print_and_set_sys_last_vars(py);
|
||||
Python::with_gil(|py| {
|
||||
main_(py).map_err(|e| {
|
||||
// We can't display Python exceptions via std::fmt::Display,
|
||||
// so print the error here manually.
|
||||
e.print_and_set_sys_last_vars(py);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -13,12 +13,12 @@ module available in your environment.
|
|||
use pyo3::prelude::*;
|
||||
|
||||
fn main() -> PyResult<()> {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let builtins = PyModule::import(py, "builtins")?;
|
||||
let total: i32 = builtins.call1("sum", (vec![1, 2, 3],))?.extract()?;
|
||||
assert_eq!(total, 6);
|
||||
Ok(())
|
||||
Python::with_gil(|py| {
|
||||
let builtins = PyModule::import(py, "builtins")?;
|
||||
let total: i32 = builtins.call1("sum", (vec![1, 2, 3],))?.extract()?;
|
||||
assert_eq!(total, 6);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -33,14 +33,14 @@ use pyo3::prelude::*;
|
|||
use pyo3::types::IntoPyDict;
|
||||
|
||||
fn main() -> Result<(), ()> {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let result = py.eval("[i * 10 for i in range(5)]", None, None).map_err(|e| {
|
||||
e.print_and_set_sys_last_vars(py);
|
||||
})?;
|
||||
let res: Vec<i64> = result.extract().unwrap();
|
||||
assert_eq!(res, vec![0, 10, 20, 30, 40]);
|
||||
Ok(())
|
||||
Python::with_gil(|py| {
|
||||
let result = py.eval("[i * 10 for i in range(5)]", None, None).map_err(|e| {
|
||||
e.print_and_set_sys_last_vars(py);
|
||||
})?;
|
||||
let res: Vec<i64> = result.extract().unwrap();
|
||||
assert_eq!(res, vec![0, 10, 20, 30, 40]);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -76,18 +76,19 @@ impl PyObjectProtocol for UserData {
|
|||
Ok(format!("User {}(id: {})", self.name, self.id))
|
||||
}
|
||||
}
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let userdata = UserData {
|
||||
id: 34,
|
||||
name: "Yu".to_string(),
|
||||
};
|
||||
let userdata = PyCell::new(py, userdata).unwrap();
|
||||
let userdata_as_tuple = (34, "Yu");
|
||||
py_run!(py, userdata userdata_as_tuple, r#"
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let userdata = UserData {
|
||||
id: 34,
|
||||
name: "Yu".to_string(),
|
||||
};
|
||||
let userdata = PyCell::new(py, userdata).unwrap();
|
||||
let userdata_as_tuple = (34, "Yu");
|
||||
py_run!(py, userdata userdata_as_tuple, r#"
|
||||
assert repr(userdata) == "User Yu(id: 34)"
|
||||
assert userdata.as_tuple() == userdata_as_tuple
|
||||
"#);
|
||||
"#);
|
||||
})
|
||||
# }
|
||||
```
|
||||
|
||||
|
@ -100,26 +101,27 @@ can be used to generate a Python module which can then be used just as if it was
|
|||
```rust
|
||||
use pyo3::{prelude::*, types::{IntoPyDict, PyModule}};
|
||||
# fn main() -> PyResult<()> {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let activators = PyModule::from_code(py, r#"
|
||||
Python::with_gil(|py| {
|
||||
let activators = PyModule::from_code(py, r#"
|
||||
def relu(x):
|
||||
"""see https://en.wikipedia.org/wiki/Rectifier_(neural_networks)"""
|
||||
return max(0.0, x)
|
||||
|
||||
def leaky_relu(x, slope=0.01):
|
||||
return x if x >= 0 else x * slope
|
||||
"#, "activators.py", "activators")?;
|
||||
"#, "activators.py", "activators")?;
|
||||
|
||||
let relu_result: f64 = activators.call1("relu", (-1.0,))?.extract()?;
|
||||
assert_eq!(relu_result, 0.0);
|
||||
let relu_result: f64 = activators.call1("relu", (-1.0,))?.extract()?;
|
||||
assert_eq!(relu_result, 0.0);
|
||||
|
||||
let kwargs = [("slope", 0.2)].into_py_dict(py);
|
||||
let lrelu_result: f64 = activators
|
||||
.call("leaky_relu", (-1.0,), Some(kwargs))?
|
||||
.extract()?;
|
||||
assert_eq!(lrelu_result, -0.2);
|
||||
# Ok(()) }
|
||||
let kwargs = [("slope", 0.2)].into_py_dict(py);
|
||||
let lrelu_result: f64 = activators
|
||||
.call("leaky_relu", (-1.0,), Some(kwargs))?
|
||||
.extract()?;
|
||||
assert_eq!(lrelu_result, -0.2);
|
||||
# Ok(())
|
||||
})
|
||||
# }
|
||||
```
|
||||
|
||||
[`Python::run`]: https://pyo3.rs/master/doc/pyo3/struct.Python.html#method.run
|
||||
|
|
|
@ -79,37 +79,36 @@ struct UserModel {
|
|||
impl Model for UserModel {
|
||||
fn set_variables(&mut self, var: &Vec<f64>) {
|
||||
println!("Rust calling Python to set the variables");
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let values: Vec<f64> = var.clone();
|
||||
let list: PyObject = values.into_py(py);
|
||||
let py_model = self.model.as_ref(py);
|
||||
py_model
|
||||
.call_method("set_variables", (list,), None)
|
||||
.unwrap();
|
||||
Python::with_gil(|py| {
|
||||
let values: Vec<f64> = var.clone();
|
||||
let list: PyObject = values.into_py(py);
|
||||
let py_model = self.model.as_ref(py);
|
||||
py_model
|
||||
.call_method("set_variables", (list,), None)
|
||||
.unwrap();
|
||||
})
|
||||
}
|
||||
|
||||
fn get_results(&self) -> Vec<f64> {
|
||||
println!("Rust calling Python to get the results");
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
self
|
||||
.model
|
||||
.as_ref(py)
|
||||
.call_method("get_results", (), None)
|
||||
.unwrap()
|
||||
.extract()
|
||||
.unwrap()
|
||||
Python::with_gil(|py| {
|
||||
self.model
|
||||
.as_ref(py)
|
||||
.call_method("get_results", (), None)
|
||||
.unwrap()
|
||||
.extract()
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
fn compute(&mut self) {
|
||||
println!("Rust calling Python to perform the computation");
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
self.model
|
||||
.as_ref(py)
|
||||
.call_method("compute", (), None)
|
||||
.unwrap();
|
||||
Python::with_gil(|py| {
|
||||
self.model
|
||||
.as_ref(py)
|
||||
.call_method("compute", (), None)
|
||||
.unwrap();
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -180,61 +179,55 @@ This wrapper will also perform the type conversions between Python and Rust.
|
|||
# impl Model for UserModel {
|
||||
# fn set_variables(&mut self, var: &Vec<f64>) {
|
||||
# println!("Rust calling Python to set the variables");
|
||||
# let gil = Python::acquire_gil();
|
||||
# let py = gil.python();
|
||||
# let values: Vec<f64> = var.clone();
|
||||
# let list: PyObject = values.into_py(py);
|
||||
# let py_model = self.model.as_ref(py);
|
||||
# py_model
|
||||
# .call_method("set_variables", (list,), None)
|
||||
# .unwrap();
|
||||
# Python::with_gil(|py| {
|
||||
# let values: Vec<f64> = var.clone();
|
||||
# let list: PyObject = values.into_py(py);
|
||||
# let py_model = self.model.as_ref(py);
|
||||
# py_model
|
||||
# .call_method("set_variables", (list,), None)
|
||||
# .unwrap();
|
||||
# })
|
||||
# }
|
||||
#
|
||||
# fn get_results(&self) -> Vec<f64> {
|
||||
# println!("Rust calling Python to get the results");
|
||||
# let gil = Python::acquire_gil();
|
||||
# let py = gil.python();
|
||||
# self
|
||||
# .model
|
||||
# .as_ref(py)
|
||||
# .call_method("get_results", (), None)
|
||||
# .unwrap()
|
||||
# .extract()
|
||||
# .unwrap()
|
||||
# Python::with_gil(|py| {
|
||||
# self.model
|
||||
# .as_ref(py)
|
||||
# .call_method("get_results", (), None)
|
||||
# .unwrap()
|
||||
# .extract()
|
||||
# .unwrap()
|
||||
# })
|
||||
# }
|
||||
#
|
||||
# fn compute(&mut self) {
|
||||
# println!("Rust calling Python to perform the computation");
|
||||
# let gil = Python::acquire_gil();
|
||||
# let py = gil.python();
|
||||
# self.model
|
||||
# .as_ref(py)
|
||||
# .call_method("compute", (), None)
|
||||
# .unwrap();
|
||||
# Python::with_gil(|py| {
|
||||
# self.model
|
||||
# .as_ref(py)
|
||||
# .call_method("compute", (), None)
|
||||
# .unwrap();
|
||||
# })
|
||||
#
|
||||
# }
|
||||
# }
|
||||
|
||||
#[pymethods]
|
||||
impl UserModel {
|
||||
pub fn set_variables(&mut self, var: Vec<f64>) -> PyResult<()> {
|
||||
pub fn set_variables(&mut self, var: Vec<f64>) {
|
||||
println!("Set variables from Python calling Rust");
|
||||
Model::set_variables(self, &var);
|
||||
Ok(())
|
||||
Model::set_variables(self, &var)
|
||||
}
|
||||
|
||||
pub fn get_results(&mut self) -> PyResult<Vec<f64>> {
|
||||
pub fn get_results(&mut self) -> Vec<f64> {
|
||||
println!("Get results from Python calling Rust");
|
||||
let results = Model::get_results(self);
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let py_results = results.into_py(py);
|
||||
Ok(py_results)
|
||||
Model::get_results(self)
|
||||
}
|
||||
|
||||
pub fn compute(&mut self) -> PyResult<()> {
|
||||
pub fn compute(&mut self) {
|
||||
println!("Compute from Python calling Rust");
|
||||
Model::compute(self);
|
||||
Ok(())
|
||||
Model::compute(self)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -353,39 +346,38 @@ We used in our `get_results` method the following call that performs the type co
|
|||
# }
|
||||
|
||||
impl Model for UserModel {
|
||||
fn get_results(&self) -> Vec<f64> {
|
||||
println!("Get results from Rust calling Python");
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
self
|
||||
.model
|
||||
.as_ref(py)
|
||||
.call_method("get_results", (), None)
|
||||
.unwrap()
|
||||
.extract()
|
||||
.unwrap()
|
||||
fn get_results(&self) -> Vec<f64> {
|
||||
println!("Rust calling Python to get the results");
|
||||
Python::with_gil(|py| {
|
||||
self.model
|
||||
.as_ref(py)
|
||||
.call_method("get_results", (), None)
|
||||
.unwrap()
|
||||
.extract()
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
# fn set_variables(&mut self, var: &Vec<f64>) {
|
||||
# println!("Rust calling Python to set the variables");
|
||||
# let gil = Python::acquire_gil();
|
||||
# let py = gil.python();
|
||||
# let values: Vec<f64> = var.clone();
|
||||
# let list: PyObject = values.into_py(py);
|
||||
# let py_model = self.model.as_ref(py);
|
||||
# py_model
|
||||
# .call_method("set_variables", (list,), None)
|
||||
# .unwrap();
|
||||
# }
|
||||
# fn set_variables(&mut self, var: &Vec<f64>) {
|
||||
# println!("Rust calling Python to set the variables");
|
||||
# Python::with_gil(|py| {
|
||||
# let values: Vec<f64> = var.clone();
|
||||
# let list: PyObject = values.into_py(py);
|
||||
# let py_model = self.model.as_ref(py);
|
||||
# py_model
|
||||
# .call_method("set_variables", (list,), None)
|
||||
# .unwrap();
|
||||
# })
|
||||
# }
|
||||
#
|
||||
# fn compute(&mut self) {
|
||||
# println!("Rust calling Python to perform the computation");
|
||||
# let gil = Python::acquire_gil();
|
||||
# let py = gil.python();
|
||||
# self.model
|
||||
# .as_ref(py)
|
||||
# .call_method("compute", (), None)
|
||||
# .unwrap();
|
||||
# }
|
||||
# fn compute(&mut self) {
|
||||
# println!("Rust calling Python to perform the computation");
|
||||
# Python::with_gil(|py| {
|
||||
# self.model
|
||||
# .as_ref(py)
|
||||
# .call_method("compute", (), None)
|
||||
# .unwrap();
|
||||
# })
|
||||
# }
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -407,42 +399,43 @@ Let's break it down in order to perform better error handling:
|
|||
# }
|
||||
|
||||
impl Model for UserModel {
|
||||
fn get_results(&self) -> Vec<f64> {
|
||||
println!("Get results from Rust calling Python");
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let py_result: &PyAny = self
|
||||
.model
|
||||
.as_ref(py)
|
||||
.call_method("get_results", (), None)
|
||||
.unwrap();
|
||||
fn get_results(&self) -> Vec<f64> {
|
||||
println!("Get results from Rust calling Python");
|
||||
Python::with_gil(|py| {
|
||||
let py_result: &PyAny = self
|
||||
.model
|
||||
.as_ref(py)
|
||||
.call_method("get_results", (), None)
|
||||
.unwrap();
|
||||
|
||||
if py_result.get_type().name() != "list" {
|
||||
panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name());
|
||||
}
|
||||
py_result.extract().unwrap()
|
||||
}
|
||||
# fn set_variables(&mut self, var: &Vec<f64>) {
|
||||
# println!("Rust calling Python to set the variables");
|
||||
# let gil = Python::acquire_gil();
|
||||
# let py = gil.python();
|
||||
# let values: Vec<f64> = var.clone();
|
||||
# let list: PyObject = values.into_py(py);
|
||||
# let py_model = self.model.as_ref(py);
|
||||
# py_model
|
||||
# .call_method("set_variables", (list,), None)
|
||||
# .unwrap();
|
||||
# }
|
||||
if py_result.get_type().name() != "list" {
|
||||
panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name());
|
||||
}
|
||||
py_result.extract()
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
# fn set_variables(&mut self, var: &Vec<f64>) {
|
||||
# println!("Rust calling Python to set the variables");
|
||||
# Python::with_gil(|py| {
|
||||
# let values: Vec<f64> = var.clone();
|
||||
# let list: PyObject = values.into_py(py);
|
||||
# let py_model = self.model.as_ref(py);
|
||||
# py_model
|
||||
# .call_method("set_variables", (list,), None)
|
||||
# .unwrap();
|
||||
# })
|
||||
# }
|
||||
#
|
||||
# fn compute(&mut self) {
|
||||
# println!("Rust calling Python to perform the computation");
|
||||
# let gil = Python::acquire_gil();
|
||||
# let py = gil.python();
|
||||
# self.model
|
||||
# .as_ref(py)
|
||||
# .call_method("compute", (), None)
|
||||
# .unwrap();
|
||||
# }
|
||||
# fn compute(&mut self) {
|
||||
# println!("Rust calling Python to perform the computation");
|
||||
# Python::with_gil(|py| {
|
||||
# self.model
|
||||
# .as_ref(py)
|
||||
# .call_method("compute", (), None)
|
||||
# .unwrap();
|
||||
# })
|
||||
# }
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -495,7 +488,7 @@ pub struct UserModel {
|
|||
#[pymodule]
|
||||
fn trait_exposure(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_class::<UserModel>()?;
|
||||
m.add_wrapped(wrap_pyfunction!(solve_wrapper)).unwrap();
|
||||
m.add_wrapped(wrap_pyfunction!(solve_wrapper))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -506,64 +499,59 @@ impl UserModel {
|
|||
UserModel { model }
|
||||
}
|
||||
|
||||
pub fn set_variables(&mut self, var: Vec<f64>) -> PyResult<()> {
|
||||
pub fn set_variables(&mut self, var: Vec<f64>) {
|
||||
println!("Set variables from Python calling Rust");
|
||||
Model::set_variables(self, &var);
|
||||
Ok(())
|
||||
Model::set_variables(self, &var)
|
||||
}
|
||||
|
||||
pub fn get_results(&mut self) -> PyResult<Vec<f64>> {
|
||||
pub fn get_results(&mut self) -> Vec<f64> {
|
||||
println!("Get results from Python calling Rust");
|
||||
let results = Model::get_results(self);
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let py_results = results.into_py(py);
|
||||
Ok(py_results)
|
||||
Model::get_results(self)
|
||||
}
|
||||
|
||||
pub fn compute(&mut self) -> PyResult<()> {
|
||||
Model::compute(self);
|
||||
Ok(())
|
||||
pub fn compute(&mut self) {
|
||||
Model::compute(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Model for UserModel {
|
||||
fn set_variables(&mut self, var: &Vec<f64>) {
|
||||
println!("Set variables from Rust calling Python");
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let values: Vec<f64> = var.clone();
|
||||
let list: PyObject = values.into_py(py);
|
||||
let py_model = self.model.as_ref(py);
|
||||
py_model
|
||||
.call_method("set_variables", (list,), None)
|
||||
.unwrap();
|
||||
println!("Rust calling Python to set the variables");
|
||||
Python::with_gil(|py| {
|
||||
let values: Vec<f64> = var.clone();
|
||||
let list: PyObject = values.into_py(py);
|
||||
let py_model = self.model.as_ref(py);
|
||||
py_model
|
||||
.call_method("set_variables", (list,), None)
|
||||
.unwrap();
|
||||
})
|
||||
}
|
||||
|
||||
fn get_results(&self) -> Vec<f64> {
|
||||
println!("Get results from Rust calling Python");
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let py_result: &PyAny = self
|
||||
.model
|
||||
.as_ref(py)
|
||||
.call_method("get_results", (), None)
|
||||
.unwrap();
|
||||
Python::with_gil(|py| {
|
||||
let py_result: &PyAny = self
|
||||
.model
|
||||
.as_ref(py)
|
||||
.call_method("get_results", (), None)
|
||||
.unwrap();
|
||||
|
||||
if py_result.get_type().name() != "list" {
|
||||
panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name());
|
||||
}
|
||||
py_result.extract().unwrap()
|
||||
if py_result.get_type().name() != "list" {
|
||||
panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name());
|
||||
}
|
||||
py_result.extract()
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn compute(&mut self) {
|
||||
println!("Compute from Rust calling Python");
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
self.model
|
||||
.as_ref(py)
|
||||
.call_method("compute", (), None)
|
||||
.unwrap();
|
||||
println!("Rust calling Python to perform the computation");
|
||||
Python::with_gil(|py| {
|
||||
self.model
|
||||
.as_ref(py)
|
||||
.call_method("compute", (), None)
|
||||
.unwrap();
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -569,8 +569,7 @@ fn buffer_readonly_error() -> PyResult<()> {
|
|||
|
||||
impl<T> Drop for PyBuffer<T> {
|
||||
fn drop(&mut self) {
|
||||
let _gil_guard = Python::acquire_gil();
|
||||
unsafe { ffi::PyBuffer_Release(&mut *self.0) }
|
||||
Python::with_gil(|_| unsafe { ffi::PyBuffer_Release(&mut *self.0) });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,31 @@ pub use gil::prepare_freethreaded_python;
|
|||
#[derive(Copy, Clone)]
|
||||
pub struct Python<'p>(PhantomData<&'p GILGuard>);
|
||||
|
||||
impl Python<'_> {
|
||||
/// Acquires the global interpreter lock, which allows access to the Python runtime. The
|
||||
/// provided closure F will be executed with the acquired `Python` marker token.
|
||||
///
|
||||
/// If the Python runtime is not already initialized, this function will initialize it.
|
||||
/// See [prepare_freethreaded_python()](fn.prepare_freethreaded_python.html) for details.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use pyo3::prelude::*;
|
||||
/// Python::with_gil(|py| -> PyResult<()> {
|
||||
/// let x: i32 = py.eval("5", None, None)?.extract()?;
|
||||
/// assert_eq!(x, 5);
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn with_gil<F, R>(f: F) -> R
|
||||
where
|
||||
F: for<'p> FnOnce(Python<'p>) -> R,
|
||||
{
|
||||
f(unsafe { gil::ensure_gil().python() })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'p> Python<'p> {
|
||||
/// Retrieves a Python instance under the assumption that the GIL is already
|
||||
/// acquired at this point, and stays acquired for the lifetime `'p`.
|
||||
|
|
Loading…
Reference in New Issue