2017-06-14 21:08:30 +00:00
|
|
|
|
// Copyright (c) 2017-present PyO3 Project and Contributors
|
2015-08-02 19:56:03 +00:00
|
|
|
|
//
|
2017-06-14 21:08:30 +00:00
|
|
|
|
// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython
|
2015-08-02 19:56:03 +00:00
|
|
|
|
|
2018-09-21 21:32:48 +00:00
|
|
|
|
//! Functionality for the code generated by the derive backend
|
|
|
|
|
|
2018-10-31 10:43:21 +00:00
|
|
|
|
use crate::err::PyResult;
|
|
|
|
|
use crate::exceptions::TypeError;
|
|
|
|
|
use crate::ffi;
|
|
|
|
|
use crate::init_once;
|
2019-02-23 17:01:22 +00:00
|
|
|
|
use crate::types::{PyDict, PyModule, PyObjectRef, PyString, PyTuple};
|
2018-10-31 10:43:21 +00:00
|
|
|
|
use crate::GILPool;
|
|
|
|
|
use crate::Python;
|
2019-02-23 17:01:22 +00:00
|
|
|
|
use crate::{IntoPyObject, PyTryFrom};
|
2018-11-02 21:35:19 +00:00
|
|
|
|
use std::ptr;
|
2015-08-02 19:56:03 +00:00
|
|
|
|
|
2015-08-02 22:06:15 +00:00
|
|
|
|
/// Description of a python parameter; used for `parse_args()`.
|
2019-02-18 19:07:41 +00:00
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct ParamDescription {
|
2015-08-02 22:06:15 +00:00
|
|
|
|
/// The name of the parameter.
|
2019-02-18 19:07:41 +00:00
|
|
|
|
pub name: &'static str,
|
2015-08-02 22:06:15 +00:00
|
|
|
|
/// Whether the parameter is optional.
|
2017-06-14 05:37:26 +00:00
|
|
|
|
pub is_optional: bool,
|
|
|
|
|
/// Whether the parameter is optional.
|
2017-06-22 19:32:01 +00:00
|
|
|
|
pub kw_only: bool,
|
2015-08-02 19:56:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse argument list
|
|
|
|
|
///
|
2015-08-02 22:06:15 +00:00
|
|
|
|
/// * fname: Name of the current function
|
|
|
|
|
/// * params: Declared parameters of the function
|
|
|
|
|
/// * args: Positional arguments
|
|
|
|
|
/// * kwargs: Keyword arguments
|
|
|
|
|
/// * output: Output array that receives the arguments.
|
|
|
|
|
/// Must have same length as `params` and must be initialized to `None`.
|
2018-09-21 21:32:48 +00:00
|
|
|
|
pub fn parse_fn_args<'p>(
|
2018-07-30 21:01:46 +00:00
|
|
|
|
fname: Option<&str>,
|
|
|
|
|
params: &[ParamDescription],
|
|
|
|
|
args: &'p PyTuple,
|
|
|
|
|
kwargs: Option<&'p PyDict>,
|
|
|
|
|
accept_args: bool,
|
|
|
|
|
accept_kwargs: bool,
|
|
|
|
|
output: &mut [Option<&'p PyObjectRef>],
|
|
|
|
|
) -> PyResult<()> {
|
2017-06-22 08:04:37 +00:00
|
|
|
|
let nargs = args.len();
|
2019-02-23 17:38:00 +00:00
|
|
|
|
let nkeywords = kwargs.map_or(0, PyDict::len);
|
2019-01-24 22:09:24 +00:00
|
|
|
|
if !accept_args && !accept_kwargs && (nargs + nkeywords > params.len()) {
|
2018-09-21 21:32:48 +00:00
|
|
|
|
return Err(TypeError::py_err(format!(
|
2018-07-30 21:01:46 +00:00
|
|
|
|
"{}{} takes at most {} argument{} ({} given)",
|
|
|
|
|
fname.unwrap_or("function"),
|
|
|
|
|
if fname.is_some() { "()" } else { "" },
|
|
|
|
|
params.len(),
|
|
|
|
|
if params.len() == 1 { "s" } else { "" },
|
|
|
|
|
nargs + nkeywords
|
|
|
|
|
)));
|
2015-08-02 19:56:03 +00:00
|
|
|
|
}
|
|
|
|
|
let mut used_keywords = 0;
|
|
|
|
|
// Iterate through the parameters and assign values to output:
|
|
|
|
|
for (i, (p, out)) in params.iter().zip(output).enumerate() {
|
2017-06-21 21:08:16 +00:00
|
|
|
|
match kwargs.and_then(|d| d.get_item(p.name)) {
|
2015-08-02 19:56:03 +00:00
|
|
|
|
Some(kwarg) => {
|
|
|
|
|
*out = Some(kwarg);
|
|
|
|
|
used_keywords += 1;
|
|
|
|
|
if i < nargs {
|
2018-09-21 21:32:48 +00:00
|
|
|
|
return Err(TypeError::py_err(format!(
|
2018-07-30 21:01:46 +00:00
|
|
|
|
"Argument given by name ('{}') and position ({})",
|
|
|
|
|
p.name,
|
|
|
|
|
i + 1
|
|
|
|
|
)));
|
2015-08-02 19:56:03 +00:00
|
|
|
|
}
|
2018-07-30 21:01:46 +00:00
|
|
|
|
}
|
2015-08-02 19:56:03 +00:00
|
|
|
|
None => {
|
2017-06-14 21:08:30 +00:00
|
|
|
|
if p.kw_only {
|
2017-07-14 01:50:34 +00:00
|
|
|
|
if !p.is_optional {
|
2018-09-21 21:32:48 +00:00
|
|
|
|
return Err(TypeError::py_err(format!(
|
2018-07-30 21:01:46 +00:00
|
|
|
|
"Required argument ('{}') is keyword only argument",
|
|
|
|
|
p.name
|
|
|
|
|
)));
|
2017-07-14 01:50:34 +00:00
|
|
|
|
}
|
2017-06-20 06:57:34 +00:00
|
|
|
|
*out = None;
|
2018-07-30 21:01:46 +00:00
|
|
|
|
} else if i < nargs {
|
2017-06-22 08:04:37 +00:00
|
|
|
|
*out = Some(args.get_item(i));
|
2015-08-02 19:56:03 +00:00
|
|
|
|
} else {
|
|
|
|
|
*out = None;
|
|
|
|
|
if !p.is_optional {
|
2018-09-21 21:32:48 +00:00
|
|
|
|
return Err(TypeError::py_err(format!(
|
2018-07-30 21:01:46 +00:00
|
|
|
|
"Required argument ('{}') (pos {}) not found",
|
|
|
|
|
p.name,
|
|
|
|
|
i + 1
|
|
|
|
|
)));
|
2015-08-02 19:56:03 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-18 07:05:49 +00:00
|
|
|
|
if !accept_kwargs && used_keywords != nkeywords {
|
2015-08-02 19:56:03 +00:00
|
|
|
|
// check for extraneous keyword arguments
|
2017-07-20 15:23:43 +00:00
|
|
|
|
for item in kwargs.unwrap().items().iter() {
|
2018-03-29 14:35:35 +00:00
|
|
|
|
let item = <PyTuple as PyTryFrom>::try_from(item)?;
|
|
|
|
|
let key = <PyString as PyTryFrom>::try_from(item.get_item(0))?.to_string()?;
|
2015-08-02 19:56:03 +00:00
|
|
|
|
if !params.iter().any(|p| p.name == key) {
|
2018-09-21 21:32:48 +00:00
|
|
|
|
return Err(TypeError::py_err(format!(
|
2018-07-30 21:01:46 +00:00
|
|
|
|
"'{}' is an invalid keyword argument for this function",
|
|
|
|
|
key
|
|
|
|
|
)));
|
2015-08-02 19:56:03 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2018-09-21 21:32:48 +00:00
|
|
|
|
|
|
|
|
|
#[cfg(Py_3)]
|
2018-11-12 21:28:45 +00:00
|
|
|
|
/// Builds a module (or null) from a user given initializer. Used for `#[pymodule]`.
|
2018-09-21 21:32:48 +00:00
|
|
|
|
pub unsafe fn make_module(
|
|
|
|
|
name: &str,
|
|
|
|
|
doc: &str,
|
|
|
|
|
initializer: impl Fn(Python, &PyModule) -> PyResult<()>,
|
|
|
|
|
) -> *mut ffi::PyObject {
|
2019-02-23 17:01:22 +00:00
|
|
|
|
use crate::IntoPyPointer;
|
2018-09-21 21:32:48 +00:00
|
|
|
|
|
|
|
|
|
init_once();
|
|
|
|
|
|
|
|
|
|
#[cfg(py_sys_config = "WITH_THREAD")]
|
|
|
|
|
// > Changed in version 3.7: This function is now called by Py_Initialize(), so you don’t have
|
|
|
|
|
// > to call it yourself anymore.
|
|
|
|
|
#[cfg(not(Py_3_7))]
|
|
|
|
|
ffi::PyEval_InitThreads();
|
|
|
|
|
|
|
|
|
|
static mut MODULE_DEF: ffi::PyModuleDef = ffi::PyModuleDef_INIT;
|
|
|
|
|
// We can't convert &'static str to *const c_char within a static initializer,
|
|
|
|
|
// so we'll do it here in the module initialization:
|
|
|
|
|
MODULE_DEF.m_name = name.as_ptr() as *const _;
|
|
|
|
|
|
|
|
|
|
let module = ffi::PyModule_Create(&mut MODULE_DEF);
|
|
|
|
|
if module.is_null() {
|
|
|
|
|
return module;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let _pool = GILPool::new();
|
|
|
|
|
let py = Python::assume_gil_acquired();
|
|
|
|
|
let module = match py.from_owned_ptr_or_err::<PyModule>(module) {
|
|
|
|
|
Ok(m) => m,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
e.restore(py);
|
|
|
|
|
return ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
module
|
|
|
|
|
.add("__doc__", doc)
|
|
|
|
|
.expect("Failed to add doc for module");
|
|
|
|
|
match initializer(py, module) {
|
|
|
|
|
Ok(_) => module.into_ptr(),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
e.restore(py);
|
|
|
|
|
ptr::null_mut()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(not(Py_3))]
|
|
|
|
|
#[doc(hidden)]
|
2018-11-12 21:28:45 +00:00
|
|
|
|
/// Builds a module (or null) from a user given initializer. Used for `#[pymodule]`.
|
2018-09-21 21:32:48 +00:00
|
|
|
|
pub unsafe fn make_module(
|
|
|
|
|
name: &str,
|
|
|
|
|
doc: &str,
|
|
|
|
|
initializer: impl Fn(Python, &PyModule) -> PyResult<()>,
|
|
|
|
|
) {
|
|
|
|
|
init_once();
|
|
|
|
|
|
|
|
|
|
#[cfg(py_sys_config = "WITH_THREAD")]
|
|
|
|
|
ffi::PyEval_InitThreads();
|
|
|
|
|
|
|
|
|
|
let _name = name.as_ptr() as *const _;
|
|
|
|
|
let _pool = GILPool::new();
|
|
|
|
|
let py = Python::assume_gil_acquired();
|
|
|
|
|
let _module = ffi::Py_InitModule(_name, ptr::null_mut());
|
|
|
|
|
if _module.is_null() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let _module = match py.from_borrowed_ptr_or_err::<PyModule>(_module) {
|
|
|
|
|
Ok(m) => m,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
e.restore(py);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_module
|
|
|
|
|
.add("__doc__", doc)
|
|
|
|
|
.expect("Failed to add doc for module");
|
|
|
|
|
if let Err(e) = initializer(py, _module) {
|
|
|
|
|
e.restore(py)
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-02-23 17:01:22 +00:00
|
|
|
|
|
|
|
|
|
/// This trait wraps a T: IntoPyObject into PyResult<T> while PyResult<T> remains PyResult<T>.
|
|
|
|
|
///
|
|
|
|
|
/// This is necessary because proc macros run before typechecking and can't decide
|
|
|
|
|
/// whether a return type is a (possibly aliased) PyResult or not. It is also quite handy because
|
|
|
|
|
/// the codegen is currently built on the assumption that all functions return a PyResult.
|
|
|
|
|
pub trait IntoPyResult<T> {
|
|
|
|
|
fn into_py_result(self) -> PyResult<T>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: IntoPyObject> IntoPyResult<T> for T {
|
|
|
|
|
fn into_py_result(self) -> PyResult<T> {
|
|
|
|
|
Ok(self)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: IntoPyObject> IntoPyResult<T> for PyResult<T> {
|
|
|
|
|
fn into_py_result(self) -> PyResult<T> {
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
}
|