Merge pull request #3232 from davidhewitt/remove-0-18-deprecations

remove all functionality deprecated in PyO3 0.18
This commit is contained in:
David Hewitt 2023-06-13 21:49:45 +00:00 committed by GitHub
commit 0b4187a672
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 100 additions and 1304 deletions

View file

@ -185,89 +185,6 @@ fn increment(x: u64, amount: Option<u64>) -> u64 {
To help avoid confusion, PyO3 requires `#[pyo3(signature = (...))]` when an `Option<T>` argument is surrounded by arguments which aren't `Option<T>`. To help avoid confusion, PyO3 requires `#[pyo3(signature = (...))]` when an `Option<T>` argument is surrounded by arguments which aren't `Option<T>`.
## Deprecated form
The `#[pyfunction]` macro can take the argument specification directly, but this method is deprecated in PyO3 0.18 because the `#[pyo3(signature)]` option offers a simpler syntax and better validation.
The `#[pymethods]` macro has an `#[args]` attribute which accepts the deprecated form.
Below are the same examples as above, but using the deprecated syntax:
```rust
# #![allow(deprecated)]
use pyo3::prelude::*;
use pyo3::types::PyDict;
#[pyfunction(kwds = "**")]
fn num_kwds(kwds: Option<&PyDict>) -> usize {
kwds.map_or(0, |dict| dict.len())
}
#[pymodule]
fn module_with_functions(py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(num_kwds, m)?).unwrap();
Ok(())
}
```
The following parameters can be passed to the `#[pyfunction]` attribute:
* `"/"`: positional-only arguments separator, each parameter defined before `"/"` is a
positional-only parameter.
Corresponds to python's `def meth(arg1, arg2, ..., /, argN..)`.
* `"*"`: var arguments separator, each parameter defined after `"*"` is a keyword-only parameter.
Corresponds to python's `def meth(*, arg1.., arg2=..)`.
* `args="*"`: "args" is var args, corresponds to Python's `def meth(*args)`. Type of the `args`
parameter has to be `&PyTuple`.
* `kwargs="**"`: "kwargs" receives keyword arguments, corresponds to Python's `def meth(**kwargs)`.
The type of the `kwargs` parameter has to be `Option<&PyDict>`.
* `arg="Value"`: arguments with default value. Corresponds to Python's `def meth(arg=Value)`.
If the `arg` argument is defined after var arguments, it is treated as a keyword-only argument.
Note that `Value` has to be valid rust code, PyO3 just inserts it into the generated
code unmodified.
Example:
```rust
# #![allow(deprecated)]
# use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};
#
# #[pyclass]
# struct MyClass {
# num: i32,
# }
#[pymethods]
impl MyClass {
#[new]
#[args(num = "-1")]
fn new(num: i32) -> Self {
MyClass { num }
}
#[args(num = "10", py_args = "*", name = "\"Hello\"", py_kwargs = "**")]
fn method(
&mut self,
num: i32,
py_args: &PyTuple,
name: &str,
py_kwargs: Option<&PyDict>,
) -> String {
let num_before = self.num;
self.num = num;
format!(
"num={} (was previously={}), py_args={:?}, name={}, py_kwargs={:?} ",
num, num_before, py_args, name, py_kwargs,
)
}
fn make_change(&mut self, num: i32) -> PyResult<String> {
self.num = num;
Ok(format!("num={}", self.num))
}
}
```
## Making the function signature available to Python ## Making the function signature available to Python
The function signature is exposed to Python via the `__text_signature__` attribute. PyO3 automatically generates this for every `#[pyfunction]` and all `#[pymethods]` directly from the Rust function, taking into account any override done with the `#[pyo3(signature = (...))]` option. The function signature is exposed to Python via the `__text_signature__` attribute. PyO3 automatically generates this for every `#[pyfunction]` and all `#[pymethods]` directly from the Rust function, taking into account any override done with the `#[pyo3(signature = (...))]` option.

View file

@ -9,6 +9,59 @@ For a detailed list of all changes, see the [CHANGELOG](changelog.md).
PyO3 0.20 has increased minimum Rust version to 1.56. This enables use of newer language features and simplifies maintenance of the project. PyO3 0.20 has increased minimum Rust version to 1.56. This enables use of newer language features and simplifies maintenance of the project.
### Required arguments are no longer accepted after optional arguments
[Trailing `Option<T>` arguments](./function/signature.md#trailing-optional-arguments) have an automatic default of `None`. To avoid unwanted changes when modifying function signatures, in PyO3 0.18 it was deprecated to have a required argument after an `Option<T>` argument without using `#[pyo3(signature = (...))]` to specify the intended defaults. In PyO3 0.20, this becomes a hard error.
Before:
```rust,ignore
#[pyfunction]
fn x_or_y(x: Option<u64>, y: u64) -> u64 {
x.unwrap_or(y)
}
```
After:
```rust
# #![allow(dead_code)]
# use pyo3::prelude::*;
#[pyfunction]
#[pyo3(signature = (x, y))] // both x and y have no defaults and are required
fn x_or_y(x: Option<u64>, y: u64) -> u64 {
x.unwrap_or(y)
}
```
### Remove deprecated function forms
In PyO3 0.18 the `#[args]` attribute for `#[pymethods]`, and directly specifying the function signature in `#[pyfunction]`, was deprecated. This functionality has been removed in PyO3 0.20.
Before:
```rust,ignore
#[pyfunction]
#[pyo3(a, b = "0", "/")]
fn add(a: u64, b: u64) -> u64 {
a + b
}
```
After:
```rust
# #![allow(dead_code)]
# use pyo3::prelude::*;
#[pyfunction]
#[pyo3(signature = (a, b=0, /))]
fn add(a: u64, b: u64) -> u64 {
a + b
}
```
## from 0.18.* to 0.19 ## from 0.18.* to 0.19
### Access to `Python` inside `__traverse__` implementations are now forbidden ### Access to `Python` inside `__traverse__` implementations are now forbidden

View file

@ -0,0 +1 @@
Remove all functionality deprecated in PyO3 0.18, including `#[args]` attribute for `#[pymethods]`.

View file

@ -3,18 +3,12 @@ use quote::{quote_spanned, ToTokens};
pub enum Deprecation { pub enum Deprecation {
PyClassTextSignature, PyClassTextSignature,
PyFunctionArguments,
PyMethodArgsAttribute,
RequiredArgumentAfterOption,
} }
impl Deprecation { impl Deprecation {
fn ident(&self, span: Span) -> syn::Ident { fn ident(&self, span: Span) -> syn::Ident {
let string = match self { let string = match self {
Deprecation::PyClassTextSignature => "PYCLASS_TEXT_SIGNATURE", Deprecation::PyClassTextSignature => "PYCLASS_TEXT_SIGNATURE",
Deprecation::PyFunctionArguments => "PYFUNCTION_ARGUMENTS",
Deprecation::PyMethodArgsAttribute => "PYMETHODS_ARGS_ATTRIBUTE",
Deprecation::RequiredArgumentAfterOption => "REQUIRED_ARGUMENT_AFTER_OPTION",
}; };
syn::Ident::new(string, span) syn::Ident::new(string, span)
} }

View file

@ -1,7 +1,6 @@
use crate::attributes::{TextSignatureAttribute, TextSignatureAttributeValue}; use crate::attributes::{TextSignatureAttribute, TextSignatureAttributeValue};
use crate::deprecations::{Deprecation, Deprecations};
use crate::params::impl_arg_params; use crate::params::impl_arg_params;
use crate::pyfunction::{DeprecatedArgs, FunctionSignature, PyFunctionArgPyO3Attributes}; use crate::pyfunction::{FunctionSignature, PyFunctionArgPyO3Attributes};
use crate::pyfunction::{PyFunctionOptions, SignatureAttribute}; use crate::pyfunction::{PyFunctionOptions, SignatureAttribute};
use crate::utils::{self, PythonDoc}; use crate::utils::{self, PythonDoc};
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
@ -236,7 +235,6 @@ pub struct FnSpec<'a> {
pub python_name: syn::Ident, pub python_name: syn::Ident,
pub signature: FunctionSignature<'a>, pub signature: FunctionSignature<'a>,
pub output: syn::Type, pub output: syn::Type,
pub deprecations: Deprecations,
pub convention: CallingConvention, pub convention: CallingConvention,
pub text_signature: Option<TextSignatureAttribute>, pub text_signature: Option<TextSignatureAttribute>,
pub unsafety: Option<syn::Token![unsafe]>, pub unsafety: Option<syn::Token![unsafe]>,
@ -281,16 +279,14 @@ impl<'a> FnSpec<'a> {
let PyFunctionOptions { let PyFunctionOptions {
text_signature, text_signature,
name, name,
mut deprecations,
signature, signature,
.. ..
} = options; } = options;
let MethodAttributes { let MethodAttributes {
ty: fn_type_attr, ty: fn_type_attr,
deprecated_args,
mut python_name, mut python_name,
} = parse_method_attributes(meth_attrs, name.map(|name| name.value.0), &mut deprecations)?; } = parse_method_attributes(meth_attrs, name.map(|name| name.value.0))?;
let (fn_type, skip_first_arg, fixed_convention) = let (fn_type, skip_first_arg, fixed_convention) =
Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?; Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;
@ -314,15 +310,9 @@ impl<'a> FnSpec<'a> {
}; };
let signature = if let Some(signature) = signature { let signature = if let Some(signature) = signature {
ensure_spanned!(
deprecated_args.is_none(),
signature.kw.span() => "cannot define both function signature and legacy arguments"
);
FunctionSignature::from_arguments_and_attribute(arguments, signature)? FunctionSignature::from_arguments_and_attribute(arguments, signature)?
} else if let Some(deprecated_args) = deprecated_args {
FunctionSignature::from_arguments_and_deprecated_args(arguments, deprecated_args)?
} else { } else {
FunctionSignature::from_arguments(arguments, &mut deprecations) FunctionSignature::from_arguments(arguments)?
}; };
let convention = let convention =
@ -335,7 +325,6 @@ impl<'a> FnSpec<'a> {
python_name, python_name,
signature, signature,
output: ty, output: ty,
deprecations,
text_signature, text_signature,
unsafety: sig.unsafety, unsafety: sig.unsafety,
}) })
@ -423,7 +412,6 @@ impl<'a> FnSpec<'a> {
ident: &proc_macro2::Ident, ident: &proc_macro2::Ident,
cls: Option<&syn::Type>, cls: Option<&syn::Type>,
) -> Result<TokenStream> { ) -> Result<TokenStream> {
let deprecations = &self.deprecations;
let self_conversion = self.tp.self_conversion(cls, ExtractErrorMode::Raise); let self_conversion = self.tp.self_conversion(cls, ExtractErrorMode::Raise);
let self_arg = self.tp.self_arg(); let self_arg = self.tp.self_arg();
let py = syn::Ident::new("_py", Span::call_site()); let py = syn::Ident::new("_py", Span::call_site());
@ -457,7 +445,6 @@ impl<'a> FnSpec<'a> {
_slf: *mut _pyo3::ffi::PyObject, _slf: *mut _pyo3::ffi::PyObject,
) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> {
let function = #rust_name; // Shadow the function name to avoid #3017 let function = #rust_name; // Shadow the function name to avoid #3017
#deprecations
#self_conversion #self_conversion
#call #call
} }
@ -475,7 +462,6 @@ impl<'a> FnSpec<'a> {
_kwnames: *mut _pyo3::ffi::PyObject _kwnames: *mut _pyo3::ffi::PyObject
) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> {
let function = #rust_name; // Shadow the function name to avoid #3017 let function = #rust_name; // Shadow the function name to avoid #3017
#deprecations
#self_conversion #self_conversion
#arg_convert #arg_convert
#call #call
@ -493,7 +479,6 @@ impl<'a> FnSpec<'a> {
_kwargs: *mut _pyo3::ffi::PyObject _kwargs: *mut _pyo3::ffi::PyObject
) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> {
let function = #rust_name; // Shadow the function name to avoid #3017 let function = #rust_name; // Shadow the function name to avoid #3017
#deprecations
#self_conversion #self_conversion
#arg_convert #arg_convert
#call #call
@ -518,7 +503,6 @@ impl<'a> FnSpec<'a> {
) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> {
use _pyo3::callback::IntoPyCallbackOutput; use _pyo3::callback::IntoPyCallbackOutput;
let function = #rust_name; // Shadow the function name to avoid #3017 let function = #rust_name; // Shadow the function name to avoid #3017
#deprecations
#arg_convert #arg_convert
let result = #call; let result = #call;
let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(#py)?; let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(#py)?;
@ -636,17 +620,14 @@ impl<'a> FnSpec<'a> {
#[derive(Debug)] #[derive(Debug)]
struct MethodAttributes { struct MethodAttributes {
ty: Option<MethodTypeAttribute>, ty: Option<MethodTypeAttribute>,
deprecated_args: Option<DeprecatedArgs>,
python_name: Option<syn::Ident>, python_name: Option<syn::Ident>,
} }
fn parse_method_attributes( fn parse_method_attributes(
attrs: &mut Vec<syn::Attribute>, attrs: &mut Vec<syn::Attribute>,
mut python_name: Option<syn::Ident>, mut python_name: Option<syn::Ident>,
deprecations: &mut Deprecations,
) -> Result<MethodAttributes> { ) -> Result<MethodAttributes> {
let mut new_attrs = Vec::new(); let mut new_attrs = Vec::new();
let mut deprecated_args = None;
let mut ty: Option<MethodTypeAttribute> = None; let mut ty: Option<MethodTypeAttribute> = None;
macro_rules! set_compound_ty { macro_rules! set_compound_ty {
@ -754,13 +735,6 @@ fn parse_method_attributes(
)) ))
} }
}; };
} else if path.is_ident("args") {
ensure_spanned!(
deprecated_args.is_none(),
nested.span() => "args may only be specified once"
);
deprecations.push(Deprecation::PyMethodArgsAttribute, path.span());
deprecated_args = Some(DeprecatedArgs::from_meta(&nested)?);
} else { } else {
new_attrs.push(attr) new_attrs.push(attr)
} }
@ -771,11 +745,7 @@ fn parse_method_attributes(
*attrs = new_attrs; *attrs = new_attrs;
Ok(MethodAttributes { Ok(MethodAttributes { ty, python_name })
ty,
deprecated_args,
python_name,
})
} }
const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments"; const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments";

View file

@ -3,19 +3,17 @@ use crate::{
self, get_pyo3_options, take_attributes, take_pyo3_options, CrateAttribute, self, get_pyo3_options, take_attributes, take_pyo3_options, CrateAttribute,
FromPyWithAttribute, NameAttribute, TextSignatureAttribute, FromPyWithAttribute, NameAttribute, TextSignatureAttribute,
}, },
deprecations::{Deprecation, Deprecations},
method::{self, CallingConvention, FnArg}, method::{self, CallingConvention, FnArg},
pymethod::check_generic, pymethod::check_generic,
utils::{ensure_not_async_fn, get_pyo3_crate}, utils::{ensure_not_async_fn, get_pyo3_crate},
}; };
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use syn::{ext::IdentExt, spanned::Spanned, NestedMeta, Result}; use syn::{ext::IdentExt, spanned::Spanned, Result};
use syn::{ use syn::{
parse::{Parse, ParseBuffer, ParseStream}, parse::{Parse, ParseStream},
token::Comma, token::Comma,
}; };
use syn::{punctuated::Punctuated, Path};
mod signature; mod signature;
@ -67,181 +65,12 @@ impl PyFunctionArgPyO3Attributes {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Argument {
PosOnlyArgsSeparator,
VarArgsSeparator,
VarArgs(syn::Path),
KeywordArgs(syn::Path),
PosOnlyArg(syn::Path, Option<String>),
Arg(syn::Path, Option<String>),
Kwarg(syn::Path, Option<String>),
}
#[derive(Debug, Default)]
pub struct DeprecatedArgs {
pub arguments: Vec<Argument>,
has_kw: bool,
has_posonly_args: bool,
has_varargs: bool,
has_kwargs: bool,
}
// Deprecated parsing mode for the signature
impl syn::parse::Parse for DeprecatedArgs {
fn parse(input: &ParseBuffer<'_>) -> syn::Result<Self> {
let attr = Punctuated::<NestedMeta, syn::Token![,]>::parse_terminated(input)?;
Self::from_meta(&attr)
}
}
impl DeprecatedArgs {
pub fn from_meta<'a>(iter: impl IntoIterator<Item = &'a NestedMeta>) -> syn::Result<Self> {
let mut slf = DeprecatedArgs::default();
for item in iter {
slf.add_item(item)?
}
Ok(slf)
}
pub fn add_item(&mut self, item: &NestedMeta) -> syn::Result<()> {
match item {
NestedMeta::Meta(syn::Meta::Path(ident)) => self.add_work(item, ident)?,
NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
self.add_name_value(item, nv)?;
}
NestedMeta::Lit(lit) => {
self.add_literal(item, lit)?;
}
NestedMeta::Meta(syn::Meta::List(list)) => bail_spanned!(
list.span() => "list is not supported as argument"
),
}
Ok(())
}
fn add_literal(&mut self, item: &NestedMeta, lit: &syn::Lit) -> syn::Result<()> {
match lit {
syn::Lit::Str(lits) if lits.value() == "*" => {
// "*"
self.vararg_is_ok(item)?;
self.has_varargs = true;
self.arguments.push(Argument::VarArgsSeparator);
Ok(())
}
syn::Lit::Str(lits) if lits.value() == "/" => {
// "/"
self.posonly_arg_is_ok(item)?;
self.has_posonly_args = true;
// any arguments _before_ this become positional-only
self.arguments.iter_mut().for_each(|a| {
if let Argument::Arg(path, name) = a {
*a = Argument::PosOnlyArg(path.clone(), name.clone());
} else {
unreachable!();
}
});
self.arguments.push(Argument::PosOnlyArgsSeparator);
Ok(())
}
_ => bail_spanned!(item.span() => "expected \"/\" or \"*\""),
}
}
fn add_work(&mut self, item: &NestedMeta, path: &Path) -> syn::Result<()> {
ensure_spanned!(
!(self.has_kw || self.has_kwargs),
item.span() => "positional argument or varargs(*) not allowed after keyword arguments"
);
if self.has_varargs {
self.arguments.push(Argument::Kwarg(path.clone(), None));
} else {
self.arguments.push(Argument::Arg(path.clone(), None));
}
Ok(())
}
fn posonly_arg_is_ok(&self, item: &NestedMeta) -> syn::Result<()> {
ensure_spanned!(
!(self.has_posonly_args || self.has_kwargs || self.has_varargs),
item.span() => "/ is not allowed after /, varargs(*), or kwargs(**)"
);
Ok(())
}
fn vararg_is_ok(&self, item: &NestedMeta) -> syn::Result<()> {
ensure_spanned!(
!(self.has_kwargs || self.has_varargs),
item.span() => "* is not allowed after varargs(*) or kwargs(**)"
);
Ok(())
}
fn kw_arg_is_ok(&self, item: &NestedMeta) -> syn::Result<()> {
ensure_spanned!(
!self.has_kwargs,
item.span() => "keyword argument or kwargs(**) is not allowed after kwargs(**)"
);
Ok(())
}
fn add_nv_common(
&mut self,
item: &NestedMeta,
name: &syn::Path,
value: String,
) -> syn::Result<()> {
self.kw_arg_is_ok(item)?;
if self.has_varargs {
// kw only
self.arguments
.push(Argument::Kwarg(name.clone(), Some(value)));
} else {
self.has_kw = true;
self.arguments
.push(Argument::Arg(name.clone(), Some(value)));
}
Ok(())
}
fn add_name_value(&mut self, item: &NestedMeta, nv: &syn::MetaNameValue) -> syn::Result<()> {
match &nv.lit {
syn::Lit::Str(litstr) => {
if litstr.value() == "*" {
// args="*"
self.vararg_is_ok(item)?;
self.has_varargs = true;
self.arguments.push(Argument::VarArgs(nv.path.clone()));
} else if litstr.value() == "**" {
// kwargs="**"
self.kw_arg_is_ok(item)?;
self.has_kwargs = true;
self.arguments.push(Argument::KeywordArgs(nv.path.clone()));
} else {
self.add_nv_common(item, &nv.path, litstr.value())?;
}
}
syn::Lit::Int(litint) => {
self.add_nv_common(item, &nv.path, format!("{}", litint))?;
}
syn::Lit::Bool(litb) => {
self.add_nv_common(item, &nv.path, format!("{}", litb.value))?;
}
_ => bail_spanned!(nv.lit.span() => "expected a string literal"),
};
Ok(())
}
}
#[derive(Default)] #[derive(Default)]
pub struct PyFunctionOptions { pub struct PyFunctionOptions {
pub pass_module: Option<attributes::kw::pass_module>, pub pass_module: Option<attributes::kw::pass_module>,
pub name: Option<NameAttribute>, pub name: Option<NameAttribute>,
pub deprecated_args: Option<DeprecatedArgs>,
pub signature: Option<SignatureAttribute>, pub signature: Option<SignatureAttribute>,
pub text_signature: Option<TextSignatureAttribute>, pub text_signature: Option<TextSignatureAttribute>,
pub deprecations: Deprecations,
pub krate: Option<CrateAttribute>, pub krate: Option<CrateAttribute>,
} }
@ -264,12 +93,7 @@ impl Parse for PyFunctionOptions {
// TODO needs duplicate check? // TODO needs duplicate check?
options.krate = Some(input.parse()?); options.krate = Some(input.parse()?);
} else { } else {
// If not recognised attribute, this is "legacy" pyfunction syntax #[pyfunction(a, b)] return Err(lookahead.error());
options
.deprecations
.push(Deprecation::PyFunctionArguments, input.span());
options.deprecated_args = Some(input.parse()?);
break;
} }
} }
@ -359,10 +183,8 @@ pub fn impl_wrap_pyfunction(
let PyFunctionOptions { let PyFunctionOptions {
pass_module, pass_module,
name, name,
deprecated_args,
signature, signature,
text_signature, text_signature,
mut deprecations,
krate, krate,
} = options; } = options;
@ -392,15 +214,9 @@ pub fn impl_wrap_pyfunction(
}; };
let signature = if let Some(signature) = signature { let signature = if let Some(signature) = signature {
ensure_spanned!(
deprecated_args.is_none(),
signature.kw.span() => "cannot define both function signature and legacy arguments"
);
FunctionSignature::from_arguments_and_attribute(arguments, signature)? FunctionSignature::from_arguments_and_attribute(arguments, signature)?
} else if let Some(deprecated_args) = deprecated_args {
FunctionSignature::from_arguments_and_deprecated_args(arguments, deprecated_args)?
} else { } else {
FunctionSignature::from_arguments(arguments, &mut deprecations) FunctionSignature::from_arguments(arguments)?
}; };
let ty = method::get_return_info(&func.sig.output); let ty = method::get_return_info(&func.sig.output);
@ -412,7 +228,6 @@ pub fn impl_wrap_pyfunction(
python_name, python_name,
signature, signature,
output: ty, output: ty,
deprecations,
text_signature, text_signature,
unsafety: func.sig.unsafety, unsafety: func.sig.unsafety,
}; };

View file

@ -1,5 +1,3 @@
use std::cmp::max;
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::ToTokens; use quote::ToTokens;
use syn::{ use syn::{
@ -12,13 +10,9 @@ use syn::{
use crate::{ use crate::{
attributes::{kw, KeywordAttribute}, attributes::{kw, KeywordAttribute},
deprecations::{Deprecation, Deprecations},
method::FnArg, method::FnArg,
pyfunction::Argument,
}; };
use super::DeprecatedArgs;
pub struct Signature { pub struct Signature {
paren_token: syn::token::Paren, paren_token: syn::token::Paren,
pub items: Punctuated<SignatureItem, Token![,]>, pub items: Punctuated<SignatureItem, Token![,]>,
@ -430,121 +424,8 @@ impl<'a> FunctionSignature<'a> {
}) })
} }
/// The difference to `from_arguments_and_signature` is that deprecated args allowed entries to be:
/// - missing
/// - out of order
pub fn from_arguments_and_deprecated_args(
mut arguments: Vec<FnArg<'a>>,
deprecated_args: DeprecatedArgs,
) -> syn::Result<Self> {
let mut varargs = None;
let mut kwargs = None;
let mut keyword_only_parameters = Vec::new();
fn first_n_argument_names(arguments: &[FnArg<'_>], count: usize) -> Vec<String> {
arguments
.iter()
.filter_map(|fn_arg| {
if fn_arg.py {
None
} else {
Some(fn_arg.name.unraw().to_string())
}
})
.take(count)
.collect()
}
// Record highest counts observed based off argument positions
let mut positional_only_arguments_count = None;
let mut positional_arguments_count = None;
let mut required_positional_parameters = 0;
let args_iter = arguments.iter_mut().filter(|arg| !arg.py); // Python<'_> arguments don't show on the Python side.
for (i, fn_arg) in args_iter.enumerate() {
if let Some(argument) = deprecated_args
.arguments
.iter()
.find(|argument| match argument {
Argument::PosOnlyArg(path, _)
| Argument::Arg(path, _)
| Argument::Kwarg(path, _)
| Argument::VarArgs(path)
| Argument::KeywordArgs(path) => path.get_ident() == Some(fn_arg.name),
_ => false,
})
{
match argument {
Argument::PosOnlyArg(_, default) | Argument::Arg(_, default) => {
if let Some(default) = default {
fn_arg.default = Some(syn::parse_str(default)?);
} else if fn_arg.optional.is_none() {
// Option<_> arguments always have an implicit None default with the old
// `#[args]`
required_positional_parameters = i + 1;
}
if matches!(argument, Argument::PosOnlyArg(_, _)) {
positional_only_arguments_count = Some(i + 1);
}
positional_arguments_count = Some(i + 1);
}
Argument::Kwarg(_, default) => {
fn_arg.default = default.as_deref().map(syn::parse_str).transpose()?;
keyword_only_parameters.push((fn_arg.name.to_string(), default.is_none()));
}
Argument::PosOnlyArgsSeparator => {}
Argument::VarArgsSeparator => {}
Argument::VarArgs(path) => {
fn_arg.is_varargs = true;
if let Some(ident) = path.get_ident() {
varargs = Some(ident.to_string());
} else {
bail_spanned!(path.span() => "expected ident for *args");
};
}
Argument::KeywordArgs(path) => {
fn_arg.is_kwargs = true;
if let Some(ident) = path.get_ident() {
kwargs = Some(ident.to_string());
} else {
bail_spanned!(path.span() => "expected ident for **kwargs");
};
}
}
} else {
// Assume this is a required positional parameter
required_positional_parameters = i + 1;
positional_arguments_count = Some(i + 1);
}
}
// fix up state based on observations above
let positional_only_parameters = positional_only_arguments_count.unwrap_or(0);
let positional_parameters = first_n_argument_names(
&arguments,
max(
positional_arguments_count.unwrap_or(0),
positional_only_arguments_count.unwrap_or(0),
),
);
Ok(FunctionSignature {
arguments,
python_signature: PythonSignature {
positional_parameters,
positional_only_parameters,
required_positional_parameters,
varargs,
keyword_only_parameters,
kwargs,
},
attribute: None,
})
}
/// Without `#[pyo3(signature)]` or `#[args]` - just take the Rust function arguments as positional. /// Without `#[pyo3(signature)]` or `#[args]` - just take the Rust function arguments as positional.
pub fn from_arguments(mut arguments: Vec<FnArg<'a>>, deprecations: &mut Deprecations) -> Self { pub fn from_arguments(arguments: Vec<FnArg<'a>>) -> syn::Result<Self> {
let mut python_signature = PythonSignature::default(); let mut python_signature = PythonSignature::default();
for arg in &arguments { for arg in &arguments {
// Python<'_> arguments don't show in Python signature // Python<'_> arguments don't show in Python signature
@ -553,13 +434,12 @@ impl<'a> FunctionSignature<'a> {
} }
if arg.optional.is_none() { if arg.optional.is_none() {
// This argument is required // This argument is required, all previous arguments must also have been required
if python_signature.required_positional_parameters ensure_spanned!(
!= python_signature.positional_parameters.len() python_signature.required_positional_parameters == python_signature.positional_parameters.len(),
{ arg.ty.span() => "required arguments after an `Option<_>` argument are ambiguous\n\
// A previous argument was not required = help: add a `#[pyo3(signature)]` annotation on this function to unambiguously specify the default values for all optional parameters"
deprecations.push(Deprecation::RequiredArgumentAfterOption, arg.name.span()); );
}
python_signature.required_positional_parameters = python_signature.required_positional_parameters =
python_signature.positional_parameters.len() + 1; python_signature.positional_parameters.len() + 1;
@ -570,20 +450,11 @@ impl<'a> FunctionSignature<'a> {
.push(arg.name.unraw().to_string()); .push(arg.name.unraw().to_string());
} }
// Fixup any `Option<_>` arguments that were made implicitly made required by the deprecated Ok(Self {
// branch above
for arg in arguments
.iter_mut()
.take(python_signature.required_positional_parameters)
{
arg.optional = None;
}
Self {
arguments, arguments,
python_signature, python_signature,
attribute: None, attribute: None,
} })
} }
fn default_value_for_parameter(&self, parameter: &str) -> String { fn default_value_for_parameter(&self, parameter: &str) -> String {

View file

@ -2,8 +2,8 @@ use std::borrow::Cow;
use crate::attributes::NameAttribute; use crate::attributes::NameAttribute;
use crate::method::{CallingConvention, ExtractErrorMode}; use crate::method::{CallingConvention, ExtractErrorMode};
use crate::utils;
use crate::utils::{ensure_not_async_fn, PythonDoc}; use crate::utils::{ensure_not_async_fn, PythonDoc};
use crate::{deprecations::Deprecations, utils};
use crate::{ use crate::{
method::{FnArg, FnSpec, FnType, SelfType}, method::{FnArg, FnSpec, FnType, SelfType},
pyfunction::PyFunctionOptions, pyfunction::PyFunctionOptions,
@ -441,13 +441,11 @@ fn impl_py_class_attribute(cls: &syn::Type, spec: &FnSpec<'_>) -> syn::Result<Me
}; };
let wrapper_ident = format_ident!("__pymethod_{}__", name); let wrapper_ident = format_ident!("__pymethod_{}__", name);
let deprecations = &spec.deprecations;
let python_name = spec.null_terminated_python_name(); let python_name = spec.null_terminated_python_name();
let associated_method = quote! { let associated_method = quote! {
fn #wrapper_ident(py: _pyo3::Python<'_>) -> _pyo3::PyResult<_pyo3::PyObject> { fn #wrapper_ident(py: _pyo3::Python<'_>) -> _pyo3::PyResult<_pyo3::PyObject> {
let function = #cls::#name; // Shadow the method name to avoid #3017 let function = #cls::#name; // Shadow the method name to avoid #3017
#deprecations
_pyo3::impl_::pymethods::OkWrap::wrap(#fncall, py) _pyo3::impl_::pymethods::OkWrap::wrap(#fncall, py)
.map_err(::core::convert::Into::into) .map_err(::core::convert::Into::into)
} }
@ -496,7 +494,6 @@ pub fn impl_py_setter_def(
property_type: PropertyType<'_>, property_type: PropertyType<'_>,
) -> Result<MethodAndMethodDef> { ) -> Result<MethodAndMethodDef> {
let python_name = property_type.null_terminated_python_name()?; let python_name = property_type.null_terminated_python_name()?;
let deprecations = property_type.deprecations();
let doc = property_type.doc(); let doc = property_type.doc();
let setter_impl = match property_type { let setter_impl = match property_type {
PropertyType::Descriptor { PropertyType::Descriptor {
@ -570,14 +567,13 @@ pub fn impl_py_setter_def(
let method_def = quote! { let method_def = quote! {
#cfg_attrs #cfg_attrs
_pyo3::class::PyMethodDefType::Setter({ _pyo3::class::PyMethodDefType::Setter(
#deprecations
_pyo3::class::PySetterDef::new( _pyo3::class::PySetterDef::new(
#python_name, #python_name,
_pyo3::impl_::pymethods::PySetter(#cls::#wrapper_ident), _pyo3::impl_::pymethods::PySetter(#cls::#wrapper_ident),
#doc #doc
) )
}) )
}; };
Ok(MethodAndMethodDef { Ok(MethodAndMethodDef {
@ -609,7 +605,6 @@ pub fn impl_py_getter_def(
property_type: PropertyType<'_>, property_type: PropertyType<'_>,
) -> Result<MethodAndMethodDef> { ) -> Result<MethodAndMethodDef> {
let python_name = property_type.null_terminated_python_name()?; let python_name = property_type.null_terminated_python_name()?;
let deprecations = property_type.deprecations();
let doc = property_type.doc(); let doc = property_type.doc();
let getter_impl = match property_type { let getter_impl = match property_type {
PropertyType::Descriptor { PropertyType::Descriptor {
@ -691,14 +686,13 @@ pub fn impl_py_getter_def(
let method_def = quote! { let method_def = quote! {
#cfg_attrs #cfg_attrs
_pyo3::class::PyMethodDefType::Getter({ _pyo3::class::PyMethodDefType::Getter(
#deprecations
_pyo3::class::PyGetterDef::new( _pyo3::class::PyGetterDef::new(
#python_name, #python_name,
_pyo3::impl_::pymethods::PyGetter(#cls::#wrapper_ident), _pyo3::impl_::pymethods::PyGetter(#cls::#wrapper_ident),
#doc #doc
) )
}) )
}; };
Ok(MethodAndMethodDef { Ok(MethodAndMethodDef {
@ -747,13 +741,6 @@ impl PropertyType<'_> {
} }
} }
fn deprecations(&self) -> Option<&Deprecations> {
match self {
PropertyType::Descriptor { .. } => None,
PropertyType::Function { spec, .. } => Some(&spec.deprecations),
}
}
fn doc(&self) -> Cow<'_, PythonDoc> { fn doc(&self) -> Cow<'_, PythonDoc> {
match self { match self {
PropertyType::Descriptor { field, .. } => { PropertyType::Descriptor { field, .. } => {

View file

@ -1,68 +0,0 @@
#![allow(deprecated)]
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};
#[pyfunction]
fn none() {}
#[pyfunction(b = "\"bar\"", "*", c = "None")]
fn simple<'a>(a: i32, b: &'a str, c: Option<&'a PyDict>) -> (i32, &'a str, Option<&'a PyDict>) {
(a, b, c)
}
#[pyfunction(b = "\"bar\"", args = "*", c = "None")]
fn simple_args<'a>(
a: i32,
b: &'a str,
c: Option<&'a PyDict>,
args: &'a PyTuple,
) -> (i32, &'a str, &'a PyTuple, Option<&'a PyDict>) {
(a, b, args, c)
}
#[pyfunction(b = "\"bar\"", c = "None", kwargs = "**")]
fn simple_kwargs<'a>(
a: i32,
b: &'a str,
c: Option<&'a PyDict>,
kwargs: Option<&'a PyDict>,
) -> (i32, &'a str, Option<&'a PyDict>, Option<&'a PyDict>) {
(a, b, c, kwargs)
}
#[pyfunction(a, b = "\"bar\"", args = "*", c = "None", kwargs = "**")]
fn simple_args_kwargs<'a>(
a: i32,
b: &'a str,
args: &'a PyTuple,
c: Option<&'a PyDict>,
kwargs: Option<&'a PyDict>,
) -> (
i32,
&'a str,
&'a PyTuple,
Option<&'a PyDict>,
Option<&'a PyDict>,
) {
(a, b, args, c, kwargs)
}
#[pyfunction(args = "*", kwargs = "**")]
fn args_kwargs<'a>(
args: &'a PyTuple,
kwargs: Option<&'a PyDict>,
) -> (&'a PyTuple, Option<&'a PyDict>) {
(args, kwargs)
}
#[pymodule]
pub fn deprecated_pyfunctions(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(none, m)?)?;
m.add_function(wrap_pyfunction!(simple, m)?)?;
m.add_function(wrap_pyfunction!(simple_args, m)?)?;
m.add_function(wrap_pyfunction!(simple_kwargs, m)?)?;
m.add_function(wrap_pyfunction!(simple_args_kwargs, m)?)?;
m.add_function(wrap_pyfunction!(args_kwargs, m)?)?;
Ok(())
}

View file

@ -5,7 +5,6 @@ use pyo3::wrap_pymodule;
pub mod buf_and_str; pub mod buf_and_str;
pub mod comparisons; pub mod comparisons;
pub mod datetime; pub mod datetime;
pub mod deprecated_pyfunctions;
pub mod dict_iter; pub mod dict_iter;
pub mod misc; pub mod misc;
pub mod objstore; pub mod objstore;
@ -23,9 +22,6 @@ fn pyo3_pytests(py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pymodule!(comparisons::comparisons))?; m.add_wrapped(wrap_pymodule!(comparisons::comparisons))?;
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
m.add_wrapped(wrap_pymodule!(datetime::datetime))?; m.add_wrapped(wrap_pymodule!(datetime::datetime))?;
m.add_wrapped(wrap_pymodule!(
deprecated_pyfunctions::deprecated_pyfunctions
))?;
m.add_wrapped(wrap_pymodule!(dict_iter::dict_iter))?; m.add_wrapped(wrap_pymodule!(dict_iter::dict_iter))?;
m.add_wrapped(wrap_pymodule!(misc::misc))?; m.add_wrapped(wrap_pymodule!(misc::misc))?;
m.add_wrapped(wrap_pymodule!(objstore::objstore))?; m.add_wrapped(wrap_pymodule!(objstore::objstore))?;
@ -44,10 +40,6 @@ fn pyo3_pytests(py: Python<'_>, m: &PyModule) -> PyResult<()> {
sys_modules.set_item("pyo3_pytests.buf_and_str", m.getattr("buf_and_str")?)?; sys_modules.set_item("pyo3_pytests.buf_and_str", m.getattr("buf_and_str")?)?;
sys_modules.set_item("pyo3_pytests.comparisons", m.getattr("comparisons")?)?; sys_modules.set_item("pyo3_pytests.comparisons", m.getattr("comparisons")?)?;
sys_modules.set_item("pyo3_pytests.datetime", m.getattr("datetime")?)?; sys_modules.set_item("pyo3_pytests.datetime", m.getattr("datetime")?)?;
sys_modules.set_item(
"pyo3_pytests.deprecated_pyfunctions",
m.getattr("deprecated_pyfunctions")?,
)?;
sys_modules.set_item("pyo3_pytests.dict_iter", m.getattr("dict_iter")?)?; sys_modules.set_item("pyo3_pytests.dict_iter", m.getattr("dict_iter")?)?;
sys_modules.set_item("pyo3_pytests.misc", m.getattr("misc")?)?; sys_modules.set_item("pyo3_pytests.misc", m.getattr("misc")?)?;
sys_modules.set_item("pyo3_pytests.objstore", m.getattr("objstore")?)?; sys_modules.set_item("pyo3_pytests.objstore", m.getattr("objstore")?)?;

View file

@ -1,61 +0,0 @@
from pyo3_pytests import deprecated_pyfunctions as pyfunctions
def none_py():
return None
def test_none_rs():
rust = pyfunctions.none()
py = none_py()
assert rust == py
def simple_py(a, b="bar", *, c=None):
return a, b, c
def test_simple_rs():
rust = pyfunctions.simple(1, "foo", c={1: 2})
py = simple_py(1, "foo", c={1: 2})
assert rust == py
def simple_args_py(a, b="bar", *args, c=None):
return a, b, args, c
def test_simple_args_rs():
rust = pyfunctions.simple_args(1, "foo", 4, 5, 6, c={1: 2})
py = simple_args_py(1, "foo", 4, 5, 6, c={1: 2})
assert rust == py
def simple_kwargs_py(a, b="bar", c=None, **kwargs):
return a, b, c, kwargs
def test_simple_kwargs_rs():
rust = pyfunctions.simple_kwargs(1, "foo", c={1: 2}, bar=4, foo=10)
py = simple_kwargs_py(1, "foo", c={1: 2}, bar=4, foo=10)
assert rust == py
def simple_args_kwargs_py(a, b="bar", *args, c=None, **kwargs):
return (a, b, args, c, kwargs)
def test_simple_args_kwargs_rs():
rust = pyfunctions.simple_args_kwargs(1, "foo", "baz", bar=4, foo=10)
py = simple_args_kwargs_py(1, "foo", "baz", bar=4, foo=10)
assert rust == py
def args_kwargs_py(*args, **kwargs):
return (args, kwargs)
def test_args_kwargs_rs():
rust = pyfunctions.args_kwargs(1, "foo", {1: 2}, bar=4, foo=10)
py = args_kwargs_py(1, "foo", {1: 2}, bar=4, foo=10)
assert rust == py

View file

@ -1,23 +1,5 @@
//! Symbols used to denote deprecated usages of PyO3's proc macros. //! Symbols used to denote deprecated usages of PyO3's proc macros.
#[deprecated(
since = "0.18.0",
note = "passing arbitrary arguments to `#[pyfunction()]` to specify the signature is being replaced by `#[pyo3(signature)]`"
)]
pub const PYFUNCTION_ARGUMENTS: () = ();
#[deprecated(
since = "0.18.0",
note = "the `#[args]` attribute for `#[methods]` is being replaced by `#[pyo3(signature)]`"
)]
pub const PYMETHODS_ARGS_ATTRIBUTE: () = ();
#[deprecated(
since = "0.18.0",
note = "required arguments after an `Option<_>` argument are ambiguous and being phased out\n= help: add a `#[pyo3(signature)]` annotation on this function to unambiguously specify the default values for all optional parameters"
)]
pub const REQUIRED_ARGUMENT_AFTER_OPTION: () = ();
#[deprecated( #[deprecated(
since = "0.19.0", since = "0.19.0",
note = "put `text_signature` on `#[new]` instead of `#[pyclass]`" note = "put `text_signature` on `#[new]` instead of `#[pyclass]`"

View file

@ -1106,15 +1106,6 @@ impl PyObject {
{ {
<T as PyTryFrom<'_>>::try_from_unchecked(self.as_ref(py)) <T as PyTryFrom<'_>>::try_from_unchecked(self.as_ref(py))
} }
/// Casts the PyObject to a concrete Python object type.
#[deprecated(since = "0.18.0", note = "use downcast() instead")]
pub fn cast_as<'p, D>(&'p self, py: Python<'p>) -> Result<&'p D, PyDowncastError<'_>>
where
D: PyTryFrom<'p>,
{
self.downcast(py)
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -799,15 +799,6 @@ impl PyAny {
unsafe { ffi::Py_TYPE(self.as_ptr()) } unsafe { ffi::Py_TYPE(self.as_ptr()) }
} }
/// Converts this `PyAny` to a concrete Python type.
#[deprecated(since = "0.18.0", note = "use the equivalent .downcast()")]
pub fn cast_as<'a, D>(&'a self) -> Result<&'a D, PyDowncastError<'_>>
where
D: PyTryFrom<'a>,
{
self.downcast()
}
/// Downcast this `PyAny` to a concrete Python type or pyclass. /// Downcast this `PyAny` to a concrete Python type or pyclass.
/// ///
/// Note that you can often avoid downcasting yourself by just specifying /// Note that you can often avoid downcasting yourself by just specifying

View file

@ -5,7 +5,6 @@
fn test_compile_errors() { fn test_compile_errors() {
let t = trybuild::TestCases::new(); let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/invalid_macro_args.rs");
t.compile_fail("tests/ui/invalid_need_module_arg_position.rs"); t.compile_fail("tests/ui/invalid_need_module_arg_position.rs");
t.compile_fail("tests/ui/invalid_property_args.rs"); t.compile_fail("tests/ui/invalid_property_args.rs");
t.compile_fail("tests/ui/invalid_proto_pymethods.rs"); t.compile_fail("tests/ui/invalid_proto_pymethods.rs");

View file

@ -162,464 +162,6 @@ fn static_method_with_args() {
}); });
} }
#[allow(deprecated)]
mod deprecated {
use super::*;
#[pyclass]
struct MethArgs {}
#[pymethods]
impl MethArgs {
#[args(test)]
fn get_optional(&self, test: Option<i32>) -> i32 {
test.unwrap_or(10)
}
fn get_optional2(&self, test: Option<i32>) -> Option<i32> {
test
}
#[args(test = "None")]
fn get_optional3(&self, test: Option<i32>) -> Option<i32> {
test
}
fn get_optional_positional(
&self,
_t1: Option<i32>,
t2: Option<i32>,
_t3: Option<i32>,
) -> Option<i32> {
t2
}
#[args(test = "10")]
fn get_default(&self, test: i32) -> i32 {
test
}
#[args("*", test = 10)]
fn get_kwarg(&self, test: i32) -> i32 {
test
}
#[args(args = "*", kwargs = "**")]
fn get_kwargs(&self, py: Python<'_>, args: &PyTuple, kwargs: Option<&PyDict>) -> PyObject {
[args.into(), kwargs.to_object(py)].to_object(py)
}
#[args(args = "*", kwargs = "**")]
fn get_pos_arg_kw(
&self,
py: Python<'_>,
a: i32,
args: &PyTuple,
kwargs: Option<&PyDict>,
) -> PyObject {
[a.to_object(py), args.into(), kwargs.to_object(py)].to_object(py)
}
#[args(a, b, "/")]
fn get_pos_only(&self, a: i32, b: i32) -> i32 {
a + b
}
#[args(a, "/", b)]
fn get_pos_only_and_pos(&self, a: i32, b: i32) -> i32 {
a + b
}
#[args(a, "/", b, c = 5)]
fn get_pos_only_and_pos_and_kw(&self, a: i32, b: i32, c: i32) -> i32 {
a + b + c
}
#[args(a, "/", "*", b)]
fn get_pos_only_and_kw_only(&self, a: i32, b: i32) -> i32 {
a + b
}
#[args(a, "/", "*", b = 3)]
fn get_pos_only_and_kw_only_with_default(&self, a: i32, b: i32) -> i32 {
a + b
}
#[args(a, "/", b, "*", c, d = 5)]
fn get_all_arg_types_together(&self, a: i32, b: i32, c: i32, d: i32) -> i32 {
a + b + c + d
}
#[args(a, "/", args = "*")]
fn get_pos_only_with_varargs(&self, a: i32, args: Vec<i32>) -> i32 {
a + args.iter().sum::<i32>()
}
#[args(a, "/", kwargs = "**")]
fn get_pos_only_with_kwargs(
&self,
py: Python<'_>,
a: i32,
kwargs: Option<&PyDict>,
) -> PyObject {
[a.to_object(py), kwargs.to_object(py)].to_object(py)
}
#[args("*", a = 2, b = 3)]
fn get_kwargs_only_with_defaults(&self, a: i32, b: i32) -> i32 {
a + b
}
#[args("*", a, b)]
fn get_kwargs_only(&self, a: i32, b: i32) -> i32 {
a + b
}
#[args("*", a = 1, b)]
fn get_kwargs_only_with_some_default(&self, a: i32, b: i32) -> i32 {
a + b
}
#[args(args = "*", a)]
fn get_args_and_required_keyword(
&self,
py: Python<'_>,
args: &PyTuple,
a: i32,
) -> PyObject {
(args, a).to_object(py)
}
#[args(a, b = 2, "*", c = 3)]
fn get_pos_arg_kw_sep1(&self, a: i32, b: i32, c: i32) -> i32 {
a + b + c
}
#[args(a, "*", b = 2, c = 3)]
fn get_pos_arg_kw_sep2(&self, a: i32, b: i32, c: i32) -> i32 {
a + b + c
}
#[args(kwargs = "**")]
fn get_pos_kw(&self, py: Python<'_>, a: i32, kwargs: Option<&PyDict>) -> PyObject {
[a.to_object(py), kwargs.to_object(py)].to_object(py)
}
// "args" can be anything that can be extracted from PyTuple
#[args(args = "*")]
fn args_as_vec(&self, args: Vec<i32>) -> i32 {
args.iter().sum()
}
}
#[test]
fn meth_args() {
Python::with_gil(|py| {
let inst = Py::new(py, MethArgs {}).unwrap();
py_run!(py, inst, "assert inst.get_optional() == 10");
py_run!(py, inst, "assert inst.get_optional(100) == 100");
py_run!(py, inst, "assert inst.get_optional2() == None");
py_run!(py, inst, "assert inst.get_optional2(100) == 100");
py_run!(py, inst, "assert inst.get_optional3() == None");
py_run!(py, inst, "assert inst.get_optional3(100) == 100");
py_run!(
py,
inst,
"assert inst.get_optional_positional(1, 2, 3) == 2"
);
py_run!(py, inst, "assert inst.get_optional_positional(1) == None");
py_run!(py, inst, "assert inst.get_default() == 10");
py_run!(py, inst, "assert inst.get_default(100) == 100");
py_run!(py, inst, "assert inst.get_kwarg() == 10");
py_expect_exception!(py, inst, "inst.get_kwarg(100)", PyTypeError);
py_run!(py, inst, "assert inst.get_kwarg(test=100) == 100");
py_run!(py, inst, "assert inst.get_kwargs() == [(), None]");
py_run!(py, inst, "assert inst.get_kwargs(1,2,3) == [(1,2,3), None]");
py_run!(
py,
inst,
"assert inst.get_kwargs(t=1,n=2) == [(), {'t': 1, 'n': 2}]"
);
py_run!(
py,
inst,
"assert inst.get_kwargs(1,2,3,t=1,n=2) == [(1,2,3), {'t': 1, 'n': 2}]"
);
py_run!(py, inst, "assert inst.get_pos_arg_kw(1) == [1, (), None]");
py_run!(
py,
inst,
"assert inst.get_pos_arg_kw(1, 2, 3) == [1, (2, 3), None]"
);
py_run!(
py,
inst,
"assert inst.get_pos_arg_kw(1, b=2) == [1, (), {'b': 2}]"
);
py_run!(py, inst, "assert inst.get_pos_arg_kw(a=1) == [1, (), None]");
py_expect_exception!(py, inst, "inst.get_pos_arg_kw()", PyTypeError);
py_expect_exception!(py, inst, "inst.get_pos_arg_kw(1, a=1)", PyTypeError);
py_expect_exception!(py, inst, "inst.get_pos_arg_kw(b=2)", PyTypeError);
py_run!(py, inst, "assert inst.get_pos_only(10, 11) == 21");
py_expect_exception!(py, inst, "inst.get_pos_only(10, b = 11)", PyTypeError);
py_expect_exception!(py, inst, "inst.get_pos_only(a = 10, b = 11)", PyTypeError);
py_run!(py, inst, "assert inst.get_pos_only_and_pos(10, 11) == 21");
py_run!(
py,
inst,
"assert inst.get_pos_only_and_pos(10, b = 11) == 21"
);
py_expect_exception!(
py,
inst,
"inst.get_pos_only_and_pos(a = 10, b = 11)",
PyTypeError
);
py_run!(
py,
inst,
"assert inst.get_pos_only_and_pos_and_kw(10, 11) == 26"
);
py_run!(
py,
inst,
"assert inst.get_pos_only_and_pos_and_kw(10, b = 11) == 26"
);
py_run!(
py,
inst,
"assert inst.get_pos_only_and_pos_and_kw(10, 11, c = 0) == 21"
);
py_run!(
py,
inst,
"assert inst.get_pos_only_and_pos_and_kw(10, b = 11, c = 0) == 21"
);
py_expect_exception!(
py,
inst,
"inst.get_pos_only_and_pos_and_kw(a = 10, b = 11)",
PyTypeError
);
py_run!(
py,
inst,
"assert inst.get_pos_only_and_kw_only(10, b = 11) == 21"
);
py_expect_exception!(
py,
inst,
"inst.get_pos_only_and_kw_only(10, 11)",
PyTypeError
);
py_expect_exception!(
py,
inst,
"inst.get_pos_only_and_kw_only(a = 10, b = 11)",
PyTypeError
);
py_run!(
py,
inst,
"assert inst.get_pos_only_and_kw_only_with_default(10) == 13"
);
py_run!(
py,
inst,
"assert inst.get_pos_only_and_kw_only_with_default(10, b = 11) == 21"
);
py_expect_exception!(
py,
inst,
"inst.get_pos_only_and_kw_only_with_default(10, 11)",
PyTypeError
);
py_expect_exception!(
py,
inst,
"inst.get_pos_only_and_kw_only_with_default(a = 10, b = 11)",
PyTypeError
);
py_run!(
py,
inst,
"assert inst.get_all_arg_types_together(10, 10, c = 10) == 35"
);
py_run!(
py,
inst,
"assert inst.get_all_arg_types_together(10, 10, c = 10, d = 10) == 40"
);
py_run!(
py,
inst,
"assert inst.get_all_arg_types_together(10, b = 10, c = 10, d = 10) == 40"
);
py_expect_exception!(
py,
inst,
"inst.get_all_arg_types_together(10, 10, 10)",
PyTypeError
);
py_expect_exception!(
py,
inst,
"inst.get_all_arg_types_together(a = 10, b = 10, c = 10)",
PyTypeError
);
py_run!(py, inst, "assert inst.get_pos_only_with_varargs(10) == 10");
py_run!(
py,
inst,
"assert inst.get_pos_only_with_varargs(10, 10) == 20"
);
py_run!(
py,
inst,
"assert inst.get_pos_only_with_varargs(10, 10, 10, 10, 10) == 50"
);
py_expect_exception!(
py,
inst,
"inst.get_pos_only_with_varargs(a = 10)",
PyTypeError
);
py_run!(
py,
inst,
"assert inst.get_pos_only_with_kwargs(10) == [10, None]"
);
py_run!(
py,
inst,
"assert inst.get_pos_only_with_kwargs(10, b = 10) == [10, {'b': 10}]"
);
py_run!(
py,
inst,
"assert inst.get_pos_only_with_kwargs(10, b = 10, c = 10, d = 10, e = 10) == [10, {'b': 10, 'c': 10, 'd': 10, 'e': 10}]"
);
py_expect_exception!(
py,
inst,
"inst.get_pos_only_with_kwargs(a = 10)",
PyTypeError
);
py_expect_exception!(
py,
inst,
"inst.get_pos_only_with_kwargs(a = 10, b = 10)",
PyTypeError
);
py_run!(py, inst, "assert inst.get_kwargs_only_with_defaults() == 5");
py_run!(
py,
inst,
"assert inst.get_kwargs_only_with_defaults(a = 8) == 11"
);
py_run!(
py,
inst,
"assert inst.get_kwargs_only_with_defaults(b = 8) == 10"
);
py_run!(
py,
inst,
"assert inst.get_kwargs_only_with_defaults(a = 1, b = 1) == 2"
);
py_run!(
py,
inst,
"assert inst.get_kwargs_only_with_defaults(b = 1, a = 1) == 2"
);
py_run!(py, inst, "assert inst.get_kwargs_only(a = 1, b = 1) == 2");
py_run!(py, inst, "assert inst.get_kwargs_only(b = 1, a = 1) == 2");
py_run!(
py,
inst,
"assert inst.get_kwargs_only_with_some_default(a = 2, b = 1) == 3"
);
py_run!(
py,
inst,
"assert inst.get_kwargs_only_with_some_default(b = 1) == 2"
);
py_run!(
py,
inst,
"assert inst.get_kwargs_only_with_some_default(b = 1, a = 2) == 3"
);
py_expect_exception!(
py,
inst,
"inst.get_kwargs_only_with_some_default()",
PyTypeError
);
py_run!(
py,
inst,
"assert inst.get_args_and_required_keyword(1, 2, a=3) == ((1, 2), 3)"
);
py_run!(
py,
inst,
"assert inst.get_args_and_required_keyword(a=1) == ((), 1)"
);
py_expect_exception!(
py,
inst,
"inst.get_args_and_required_keyword()",
PyTypeError
);
py_run!(py, inst, "assert inst.get_pos_arg_kw_sep1(1) == 6");
py_run!(py, inst, "assert inst.get_pos_arg_kw_sep1(1, 2) == 6");
py_run!(
py,
inst,
"assert inst.get_pos_arg_kw_sep1(1, 2, c=13) == 16"
);
py_run!(
py,
inst,
"assert inst.get_pos_arg_kw_sep1(a=1, b=2, c=13) == 16"
);
py_run!(
py,
inst,
"assert inst.get_pos_arg_kw_sep1(b=2, c=13, a=1) == 16"
);
py_run!(
py,
inst,
"assert inst.get_pos_arg_kw_sep1(c=13, b=2, a=1) == 16"
);
py_expect_exception!(py, inst, "inst.get_pos_arg_kw_sep1(1, 2, 3)", PyTypeError);
py_run!(py, inst, "assert inst.get_pos_arg_kw_sep2(1) == 6");
py_run!(
py,
inst,
"assert inst.get_pos_arg_kw_sep2(1, b=12, c=13) == 26"
);
py_expect_exception!(py, inst, "inst.get_pos_arg_kw_sep2(1, 2)", PyTypeError);
py_run!(py, inst, "assert inst.get_pos_kw(1, b=2) == [1, {'b': 2}]");
py_expect_exception!(py, inst, "inst.get_pos_kw(1,2)", PyTypeError);
py_run!(py, inst, "assert inst.args_as_vec(1,2,3) == 6");
});
}
}
#[pyclass] #[pyclass]
struct MethSignature {} struct MethSignature {}

View file

@ -478,43 +478,6 @@ fn use_pyfunction() {
}) })
} }
#[test]
#[allow(deprecated)]
fn required_argument_after_option() {
#[pyfunction]
pub fn foo(x: Option<i32>, y: i32) -> i32 {
y + x.unwrap_or_default()
}
Python::with_gil(|py| {
let f = wrap_pyfunction!(foo, py).unwrap();
// it is an error to call this function with no arguments
py_expect_exception!(
py,
f,
"f()",
PyTypeError,
"foo() missing 2 required positional arguments: 'x' and 'y'"
);
// it is an error to call this function with one argument
py_expect_exception!(
py,
f,
"f(None)",
PyTypeError,
"foo() missing 1 required positional argument: 'y'"
);
// ok to call with two arguments
py_assert!(py, f, "f(None, 5) == 5");
// ok to call with keyword arguments
py_assert!(py, f, "f(x=None, y=5) == 5");
})
}
#[pyclass] #[pyclass]
struct Key(String); struct Key(String);

View file

@ -1,49 +0,0 @@
#![cfg(feature = "macros")]
#![allow(deprecated)]
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};
mod common;
#[pyclass]
struct MyClass {}
#[pymethods]
impl MyClass {
#[staticmethod]
#[args(args = "*")]
fn test_args(args: &PyTuple) -> &PyTuple {
args
}
#[staticmethod]
#[args(kwargs = "**")]
fn test_kwargs(kwargs: Option<&PyDict>) -> Option<&PyDict> {
kwargs
}
}
#[test]
fn variable_args() {
Python::with_gil(|py| {
let my_obj = py.get_type::<MyClass>();
py_assert!(py, my_obj, "my_obj.test_args() == ()");
py_assert!(py, my_obj, "my_obj.test_args(1) == (1,)");
py_assert!(py, my_obj, "my_obj.test_args(1, 2) == (1, 2)");
});
}
#[test]
fn variable_kwargs() {
Python::with_gil(|py| {
let my_obj = py.get_type::<MyClass>();
py_assert!(py, my_obj, "my_obj.test_kwargs() == None");
py_assert!(py, my_obj, "my_obj.test_kwargs(test=1) == {'test': 1}");
py_assert!(
py,
my_obj,
"my_obj.test_kwargs(test1=1, test2=2) == {'test1':1, 'test2':2}"
);
});
}

View file

@ -2,25 +2,8 @@
use pyo3::prelude::*; use pyo3::prelude::*;
#[pyfunction(_opt = "None", x = "5")]
fn function_with_args(_opt: Option<i32>, _x: i32) {}
#[pyfunction]
fn function_with_required_after_option(_opt: Option<i32>, _x: i32) {}
#[pyclass] #[pyclass]
#[pyo3(text_signature = "()")]
struct MyClass; struct MyClass;
#[pymethods] fn main() {}
impl MyClass {
#[args(_opt = "None", x = "5")]
fn function_with_args(&self, _opt: Option<i32>, _x: i32) {}
#[args(_has_default = 1)]
fn default_arg_before_required_deprecated(&self, _has_default: isize, _required: isize) {}
}
fn main() {
function_with_required_after_option(None, 0);
function_with_args(None, 0);
}

View file

@ -1,30 +1,11 @@
error: use of deprecated constant `pyo3::impl_::deprecations::PYFUNCTION_ARGUMENTS`: passing arbitrary arguments to `#[pyfunction()]` to specify the signature is being replaced by `#[pyo3(signature)]` error: use of deprecated constant `pyo3::impl_::deprecations::PYCLASS_TEXT_SIGNATURE`: put `text_signature` on `#[new]` instead of `#[pyclass]`
--> tests/ui/deprecations.rs:5:14 --> tests/ui/deprecations.rs:6:8
| |
5 | #[pyfunction(_opt = "None", x = "5")] 6 | #[pyo3(text_signature = "()")]
| ^^^^ | ^^^^^^^^^^^^^^
| |
note: the lint level is defined here note: the lint level is defined here
--> tests/ui/deprecations.rs:1:9 --> tests/ui/deprecations.rs:1:9
| |
1 | #![deny(deprecated)] 1 | #![deny(deprecated)]
| ^^^^^^^^^^ | ^^^^^^^^^^
error: use of deprecated constant `pyo3::impl_::deprecations::REQUIRED_ARGUMENT_AFTER_OPTION`: required arguments after an `Option<_>` argument are ambiguous and being phased out
= help: add a `#[pyo3(signature)]` annotation on this function to unambiguously specify the default values for all optional parameters
--> tests/ui/deprecations.rs:9:59
|
9 | fn function_with_required_after_option(_opt: Option<i32>, _x: i32) {}
| ^^
error: use of deprecated constant `pyo3::impl_::deprecations::PYMETHODS_ARGS_ATTRIBUTE`: the `#[args]` attribute for `#[methods]` is being replaced by `#[pyo3(signature)]`
--> tests/ui/deprecations.rs:16:7
|
16 | #[args(_opt = "None", x = "5")]
| ^^^^
error: use of deprecated constant `pyo3::impl_::deprecations::PYMETHODS_ARGS_ATTRIBUTE`: the `#[args]` attribute for `#[methods]` is being replaced by `#[pyo3(signature)]`
--> tests/ui/deprecations.rs:19:7
|
19 | #[args(_has_default = 1)]
| ^^^^

View file

@ -1,33 +0,0 @@
use pyo3::prelude::*;
#[pyfunction(a = 5, b)]
fn pos_after_kw(py: Python<'_>, a: i32, b: i32) -> PyObject {
[a.to_object(py), vararg.into()].to_object(py)
}
#[pyfunction(kwargs = "**", a = 5)]
fn kw_after_kwargs(py: Python<'_>, kwargs: &PyDict, a: i32) -> PyObject {
[a.to_object(py), vararg.into()].to_object(py)
}
#[pyfunction(a, "*", b, "/", c)]
fn pos_only_after_kw_only(py: Python<'_>, a: i32, b: i32, c: i32) -> i32 {
a + b + c
}
#[pyfunction(a, args="*", "/", b)]
fn pos_only_after_args(py: Python<'_>, a: i32, args: Vec<i32>, b: i32) -> i32 {
a + b + c
}
#[pyfunction(a, kwargs="**", "/", b)]
fn pos_only_after_kwargs(py: Python<'_>, a: i32, args: Vec<i32>, b: i32) -> i32 {
a + b
}
#[pyfunction(kwargs = "**", "*", a)]
fn kw_only_after_kwargs(py: Python<'_>, kwargs: &PyDict, a: i32) -> PyObject {
[a.to_object(py), vararg.into()].to_object(py)
}
fn main() {}

View file

@ -1,35 +0,0 @@
error: positional argument or varargs(*) not allowed after keyword arguments
--> tests/ui/invalid_macro_args.rs:3:21
|
3 | #[pyfunction(a = 5, b)]
| ^
error: keyword argument or kwargs(**) is not allowed after kwargs(**)
--> tests/ui/invalid_macro_args.rs:8:29
|
8 | #[pyfunction(kwargs = "**", a = 5)]
| ^
error: / is not allowed after /, varargs(*), or kwargs(**)
--> tests/ui/invalid_macro_args.rs:13:25
|
13 | #[pyfunction(a, "*", b, "/", c)]
| ^^^
error: / is not allowed after /, varargs(*), or kwargs(**)
--> tests/ui/invalid_macro_args.rs:18:27
|
18 | #[pyfunction(a, args="*", "/", b)]
| ^^^
error: / is not allowed after /, varargs(*), or kwargs(**)
--> tests/ui/invalid_macro_args.rs:23:30
|
23 | #[pyfunction(a, kwargs="**", "/", b)]
| ^^^
error: * is not allowed after varargs(*) or kwargs(**)
--> tests/ui/invalid_macro_args.rs:28:29
|
28 | #[pyfunction(kwargs = "**", "*", a)]
| ^^^

View file

@ -16,11 +16,11 @@ error: expected argument from function definition `y` but got argument `x`
13 | #[pyo3(signature = (x))] 13 | #[pyo3(signature = (x))]
| ^ | ^
error: cannot define both function signature and legacy arguments error: expected one of: `name`, `pass_module`, `signature`, `text_signature`, `crate`
--> tests/ui/invalid_pyfunction_signatures.rs:19:8 --> tests/ui/invalid_pyfunction_signatures.rs:18:14
| |
19 | #[pyo3(signature = (x))] 18 | #[pyfunction(x)]
| ^^^^^^^^^ | ^
error: `*args` not allowed after `*` error: `*args` not allowed after `*`
--> tests/ui/invalid_pyfunction_signatures.rs:25:24 --> tests/ui/invalid_pyfunction_signatures.rs:25:24
@ -52,8 +52,8 @@ error: arguments of type `Python` must not be part of the signature
47 | #[pyfunction(signature = (py))] 47 | #[pyfunction(signature = (py))]
| ^^ | ^^
error: cannot define both function signature and legacy arguments error: cannot find attribute `args` in this scope
--> tests/ui/invalid_pyfunction_signatures.rs:58:12 --> tests/ui/invalid_pyfunction_signatures.rs:57:7
| |
58 | #[pyo3(signature = (x))] 57 | #[args(x)]
| ^^^^^^^^^ | ^^^^

View file

@ -15,4 +15,7 @@ fn wildcard_argument(_: i32) {}
#[pyfunction] #[pyfunction]
fn destructured_argument((a, b): (i32, i32)) {} fn destructured_argument((a, b): (i32, i32)) {}
#[pyfunction]
fn function_with_required_after_option(_opt: Option<i32>, _x: i32) {}
fn main() {} fn main() {}

View file

@ -29,3 +29,10 @@ error: destructuring in arguments is not supported
| |
16 | fn destructured_argument((a, b): (i32, i32)) {} 16 | fn destructured_argument((a, b): (i32, i32)) {}
| ^^^^^^ | ^^^^^^
error: required arguments after an `Option<_>` argument are ambiguous
= help: add a `#[pyo3(signature)]` annotation on this function to unambiguously specify the default values for all optional parameters
--> tests/ui/invalid_pyfunctions.rs:19:63
|
19 | fn function_with_required_after_option(_opt: Option<i32>, _x: i32) {}
| ^^^