pyo3/src/argparse.rs

103 lines
3.6 KiB
Rust
Raw Normal View History

// Copyright (c) 2017-present PyO3 Project and Contributors
2015-08-02 19:56:03 +00:00
//
// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython
2015-08-02 19:56:03 +00:00
2017-06-24 08:47:36 +00:00
//! Python argument parsing
2017-05-25 03:31:51 +00:00
use ffi;
2017-07-27 03:29:07 +00:00
use err::PyResult;
2017-07-29 06:19:00 +00:00
use python::Python;
use conversion::PyTryFrom;
2017-06-24 15:28:31 +00:00
use objects::{PyObjectRef, PyTuple, PyDict, PyString, exc};
2015-08-02 19:56:03 +00:00
2017-07-14 01:50:34 +00:00
#[derive(Debug)]
/// Description of a python parameter; used for `parse_args()`.
2015-08-02 19:56:03 +00:00
pub struct ParamDescription<'a> {
/// The name of the parameter.
pub name: &'a str,
/// Whether the parameter is optional.
2017-06-14 05:37:26 +00:00
pub is_optional: bool,
/// Whether the parameter is optional.
pub kw_only: bool,
2015-08-02 19:56:03 +00:00
}
/// Parse argument list
///
/// * 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`.
pub fn parse_args<'p>(fname: Option<&str>, params: &[ParamDescription],
2017-06-03 01:58:16 +00:00
args: &'p PyTuple, kwargs: Option<&'p PyDict>,
2017-05-25 03:31:51 +00:00
accept_args: bool, accept_kwargs: bool,
2017-06-24 15:28:31 +00:00
output: &mut[Option<&'p PyObjectRef>]) -> PyResult<()>
2015-08-02 19:56:03 +00:00
{
2017-06-22 08:04:37 +00:00
let nargs = args.len();
2017-06-21 21:08:16 +00:00
let nkeywords = kwargs.map_or(0, |d| d.len());
2017-05-18 07:05:49 +00:00
if !accept_args && (nargs + nkeywords > params.len()) {
2017-07-27 03:29:07 +00:00
return Err(exc::TypeError::new(
2015-08-02 19:56:03 +00:00
format!("{}{} takes at most {} argument{} ({} given)",
fname.unwrap_or("function"),
if fname.is_some() { "()" } else { "" },
params.len(),
if params.len() == 1 { "s" } else { "" },
nargs + nkeywords
2017-05-25 03:31:51 +00:00
)));
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 {
2017-07-27 03:29:07 +00:00
return Err(exc::TypeError::new(
2017-06-07 02:26:59 +00:00
format!("Argument given by name ('{}') and position ({})", p.name, i+1)));
2015-08-02 19:56:03 +00:00
}
},
None => {
if p.kw_only {
2017-07-14 01:50:34 +00:00
if !p.is_optional {
2017-07-27 03:29:07 +00:00
return Err(exc::TypeError::new(
format!("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;
}
2017-06-20 06:57:34 +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 {
2017-07-27 03:29:07 +00:00
return Err(exc::TypeError::new(
format!("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() {
2017-07-29 06:19:00 +00:00
let item = PyTuple::try_from(item)?;
let key = PyString::try_from(item.get_item(0))?.to_string()?;
2015-08-02 19:56:03 +00:00
if !params.iter().any(|p| p.name == key) {
2017-07-27 03:29:07 +00:00
return Err(exc::TypeError::new(
2017-07-20 15:23:43 +00:00
format!("'{}' is an invalid keyword argument for this function", key)));
2015-08-02 19:56:03 +00:00
}
}
}
Ok(())
}
2017-05-28 05:45:48 +00:00
#[inline]
#[doc(hidden)]
2017-07-18 11:28:49 +00:00
pub unsafe fn get_kwargs(py: Python, ptr: *mut ffi::PyObject) -> Option<&PyDict> {
if ptr.is_null() {
None
} else {
Some(py.from_borrowed_ptr::<PyDict>(ptr))
}
}