text_signature: move to `#[pyo3(text_signature = "...")]`

This commit is contained in:
David Hewitt 2021-06-05 16:28:31 +01:00
parent a5810eaffa
commit cec4c2d2e9
19 changed files with 317 additions and 171 deletions

View File

@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Add FFI definition `PyDateTime_TimeZone_UTC`. [#1572](https://github.com/PyO3/pyo3/pull/1572)
- Add support for `#[pyclass(extends=Exception)]`. [#1591](https://github.com/PyO3/pyo3/pull/1591)
- Add support for extracting `PathBuf` from `pathlib.Path`. [#1654](https://github.com/PyO3/pyo3/pull/1654)
- Add `#[pyo3(text_signature = "...")]` syntax for setting text signature. [#1658](https://github.com/PyO3/pyo3/pull/1658)
### Changed
- Allow only one `#[pymethods]` block per `#[pyclass]` by default, to simplify the proc macro implementations. Add `multiple-pymethods` feature to opt-in to the more complex full behavior. [#1457](https://github.com/PyO3/pyo3/pull/1457)
@ -49,6 +50,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Deprecate string-literal second argument to `#[pyfn(m, "name")]`. [#1610](https://github.com/PyO3/pyo3/pull/1610)
- No longer call `PyEval_InitThreads()` in `#[pymodule]` init code. [#1630](https://github.com/PyO3/pyo3/pull/1630)
- Use `METH_FASTCALL` argument passing convention, when possible, to improve `#[pyfunction]` performance. [#1619](https://github.com/PyO3/pyo3/pull/1619)
- Deprecate `#[text_signature = "..."]` attributes in favor of `#[pyo3(text_signature = "...")]`. [#1658](https://github.com/PyO3/pyo3/pull/1658)
### Removed
- Remove deprecated exception names `BaseException` etc. [#1426](https://github.com/PyO3/pyo3/pull/1426)

View File

@ -41,10 +41,10 @@ There are two main ways to test, build and distribute your module as a Python pa
### Manual builds
You can also symlink (or copy) and rename the shared library from the `target` folder:
You can also symlink (or copy) and rename the shared library from the `target` folder:
- on macOS, rename `libyour_module.dylib` to `your_module.so`.
- on Windows, rename `libyour_module.dll` to `your_module.pyd`.
- on Linux, rename `libyour_module.so` to `your_module.so`.
- on Linux, rename `libyour_module.so` to `your_module.so`.
You can then open a Python shell in the same folder and you'll be able to use `import your_module`.
@ -86,7 +86,7 @@ As an advanced feature, you can build PyO3 wheel without calling Python interpre
Due to limitations in the Python API, there are a few `pyo3` features that do
not work when compiling for `abi3`. These are:
- `#[text_signature]` does not work on classes until Python 3.10 or greater.
- `#[pyo3(text_signature = "...")]` does not work on classes until Python 3.10 or greater.
- The `dict` and `weakref` options on classes are not supported until Python 3.9 or greater.
- The buffer API is not supported.
- Optimizations which rely on knowledge of the exact Python version compiled against.

View File

@ -117,7 +117,7 @@ fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
## Making the function signature available to Python
In order to make the function signature available to Python to be retrieved via
`inspect.signature`, use the `#[text_signature]` annotation as in the example
`inspect.signature`, use the `#[pyo3(text_signature)]` annotation as in the example
below. The `/` signifies the end of positional-only arguments. (This
is not a feature of this library in particular, but the general format used by
CPython for annotating signatures of built-in functions.)
@ -127,7 +127,7 @@ use pyo3::prelude::*;
/// This function adds two unsigned 64-bit integers.
#[pyfunction]
#[text_signature = "(a, b, /)"]
#[pyo3(text_signature = "(a, b, /)")]
fn add(a: u64, b: u64) -> u64 {
a + b
}
@ -142,7 +142,7 @@ use pyo3::types::PyType;
// it works even if the item is not documented:
#[pyclass]
#[text_signature = "(c, d, /)"]
#[pyo3(text_signature = "(c, d, /)")]
struct MyClass {}
#[pymethods]
@ -154,17 +154,17 @@ impl MyClass {
Self {}
}
// the self argument should be written $self
#[text_signature = "($self, e, f)"]
#[pyo3(text_signature = "($self, e, f)")]
fn my_method(&self, e: i32, f: i32) -> i32 {
e + f
}
#[classmethod]
#[text_signature = "(cls, e, f)"]
#[pyo3(text_signature = "(cls, e, f)")]
fn my_class_method(cls: &PyType, e: i32, f: i32) -> i32 {
e + f
}
#[staticmethod]
#[text_signature = "(e, f)"]
#[pyo3(text_signature = "(e, f)")]
fn my_static_method(e: i32, f: i32) -> i32 {
e + f
}
@ -180,7 +180,7 @@ Alternatively, simply make sure the first line of your docstring is
formatted like in the following example. Please note that the newline after the
`--` is mandatory. The `/` signifies the end of positional-only arguments.
`#[text_signature]` should be preferred, since it will override automatically
`#[pyo3(text_signature)]` should be preferred, since it will override automatically
generated signatures when those are added in a future version of PyO3.
```rust

View File

@ -18,6 +18,7 @@ pub mod kw {
syn::custom_keyword!(name);
syn::custom_keyword!(set);
syn::custom_keyword!(signature);
syn::custom_keyword!(text_signature);
syn::custom_keyword!(transparent);
}
@ -45,6 +46,23 @@ impl Parse for NameAttribute {
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TextSignatureAttribute {
pub kw: kw::text_signature,
pub eq_token: Token![=],
pub lit: LitStr,
}
impl Parse for TextSignatureAttribute {
fn parse(input: ParseStream) -> Result<Self> {
Ok(TextSignatureAttribute {
kw: input.parse()?,
eq_token: input.parse()?,
lit: input.parse()?,
})
}
}
pub fn get_pyo3_options<T: Parse>(attr: &syn::Attribute) -> Result<Option<Punctuated<T, Comma>>> {
if is_attribute_ident(attr, "pyo3") {
attr.parse_args_with(Punctuated::parse_terminated).map(Some)
@ -112,3 +130,49 @@ pub fn get_deprecated_name_attribute(
_ => Ok(None),
}
}
pub fn get_deprecated_text_signature_attribute(
attr: &syn::Attribute,
deprecations: &mut Deprecations,
) -> syn::Result<Option<TextSignatureAttribute>> {
match attr.parse_meta() {
Ok(syn::Meta::NameValue(syn::MetaNameValue {
path,
lit: syn::Lit::Str(lit),
..
})) if path.is_ident("text_signature") => {
let text_signature = TextSignatureAttribute {
kw: syn::parse_quote!(text_signature),
eq_token: syn::parse_quote!(=),
lit,
};
deprecations.push(
crate::deprecations::Deprecation::TextSignatureAttribute,
attr.span(),
);
Ok(Some(text_signature))
}
_ => Ok(None),
}
}
pub fn take_deprecated_text_signature_attribute(
attrs: &mut Vec<syn::Attribute>,
deprecations: &mut Deprecations,
) -> syn::Result<Option<TextSignatureAttribute>> {
let mut text_signature = None;
let mut attrs_out = Vec::with_capacity(attrs.len());
for attr in attrs.drain(..) {
if let Some(value) = get_deprecated_text_signature_attribute(&attr, deprecations)? {
ensure_spanned!(
text_signature.is_none(),
attr.span() => "text_signature attribute already specified previously"
);
text_signature = Some(value);
} else {
attrs_out.push(attr);
}
}
*attrs = attrs_out;
Ok(text_signature)
}

View File

@ -4,6 +4,7 @@ use quote::{quote_spanned, ToTokens};
pub enum Deprecation {
NameAttribute,
PyfnNameArgument,
TextSignatureAttribute,
}
impl Deprecation {
@ -11,6 +12,7 @@ impl Deprecation {
let string = match self {
Deprecation::NameAttribute => "NAME_ATTRIBUTE",
Deprecation::PyfnNameArgument => "PYFN_NAME_ARGUMENT",
Deprecation::TextSignatureAttribute => "TEXT_SIGNATURE_ATTRIBUTE",
};
syn::Ident::new(string, span)
}

View File

@ -1,5 +1,6 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::attributes::TextSignatureAttribute;
use crate::pyfunction::PyFunctionOptions;
use crate::pyfunction::{PyFunctionArgPyO3Attributes, PyFunctionSignature};
use crate::utils;
@ -209,13 +210,19 @@ impl<'a> FnSpec<'a> {
}
let (fn_type, skip_first_arg) = Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;
Self::ensure_text_signature_on_valid_method(&fn_type, options.text_signature.as_ref())?;
let name = &sig.ident;
let ty = get_return_info(&sig.output);
let python_name = python_name.as_ref().unwrap_or(name).unraw();
let text_signature = Self::parse_text_signature(meth_attrs, &fn_type, &python_name)?;
let doc = utils::get_doc(&meth_attrs, text_signature, true)?;
let doc = utils::get_doc(
&meth_attrs,
options
.text_signature
.as_ref()
.map(|attr| (&python_name, attr)),
)?;
let arguments = if skip_first_arg {
sig.inputs
@ -246,36 +253,27 @@ impl<'a> FnSpec<'a> {
syn::LitStr::new(&format!("{}\0", self.python_name), self.python_name.span())
}
fn parse_text_signature(
meth_attrs: &mut Vec<syn::Attribute>,
fn ensure_text_signature_on_valid_method(
fn_type: &FnType,
python_name: &syn::Ident,
) -> syn::Result<Option<syn::LitStr>> {
let mut parse_erroneous_text_signature = |error_msg: &str| {
// try to parse anyway to give better error messages
if let Some(text_signature) =
utils::parse_text_signature_attrs(meth_attrs, &python_name)?
{
bail_spanned!(text_signature.span() => error_msg)
} else {
Ok(None)
text_signature: Option<&TextSignatureAttribute>,
) -> syn::Result<()> {
if let Some(text_signature) = text_signature {
match &fn_type {
FnType::FnNew => bail_spanned!(
text_signature.kw.span() =>
"text_signature not allowed on __new__; if you want to add a signature on \
__new__, put it on the struct definition instead"
),
FnType::FnCall(_)
| FnType::Getter(_)
| FnType::Setter(_)
| FnType::ClassAttribute => bail_spanned!(
text_signature.kw.span() => "text_signature not allowed with this method type"
),
_ => {}
}
};
let text_signature = match &fn_type {
FnType::Fn(_) | FnType::FnClass | FnType::FnStatic => {
utils::parse_text_signature_attrs(&mut *meth_attrs, &python_name)?
}
FnType::FnNew => parse_erroneous_text_signature(
"text_signature not allowed on __new__; if you want to add a signature on \
__new__, put it on the struct definition instead",
)?,
FnType::FnCall(_) | FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => {
parse_erroneous_text_signature("text_signature not allowed with this method type")?
}
};
Ok(text_signature)
}
Ok(())
}
fn parse_fn_type(

View File

@ -14,6 +14,7 @@ use syn::{parse::Parse, spanned::Spanned, token::Comma, Ident, Path};
/// module
pub fn py_init(fnname: &Ident, name: &Ident, doc: syn::LitStr) -> TokenStream {
let cb_name = Ident::new(&format!("PyInit_{}", name), Span::call_site());
assert!(doc.value().ends_with('\0'));
quote! {
#[no_mangle]
@ -23,7 +24,7 @@ pub fn py_init(fnname: &Ident, name: &Ident, doc: syn::LitStr) -> TokenStream {
pub unsafe extern "C" fn #cb_name() -> *mut pyo3::ffi::PyObject {
use pyo3::derive_utils::ModuleDef;
static NAME: &str = concat!(stringify!(#name), "\0");
static DOC: &str = concat!(#doc, "\0");
static DOC: &str = #doc;
static MODULE_DEF: ModuleDef = unsafe { ModuleDef::new(NAME, DOC) };
pyo3::callback::handle_panic(|_py| { MODULE_DEF.make_module(_py, #fnname) })

View File

@ -1,6 +1,10 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::attributes::{self, take_pyo3_options, NameAttribute};
use crate::attributes::{
self, take_deprecated_text_signature_attribute, take_pyo3_options, NameAttribute,
TextSignatureAttribute,
};
use crate::deprecations::Deprecations;
use crate::pyimpl::PyClassMethodsType;
use crate::pymethod::{impl_py_getter_def, impl_py_setter_def, PropertyType};
use crate::utils;
@ -162,16 +166,71 @@ impl PyClassArgs {
}
}
#[derive(Default)]
pub struct PyClassPyO3Options {
pub text_signature: Option<TextSignatureAttribute>,
pub deprecations: Deprecations,
}
enum PyClassPyO3Option {
TextSignature(TextSignatureAttribute),
}
impl Parse for PyClassPyO3Option {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(attributes::kw::text_signature) {
input.parse().map(PyClassPyO3Option::TextSignature)
} else {
Err(lookahead.error())
}
}
}
impl PyClassPyO3Options {
pub fn take_pyo3_options(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
let mut options: PyClassPyO3Options = Default::default();
for option in take_pyo3_options(attrs)? {
match option {
PyClassPyO3Option::TextSignature(text_signature) => {
options.set_text_signature(text_signature)?;
}
}
}
Ok(options)
}
pub fn set_text_signature(
&mut self,
text_signature: TextSignatureAttribute,
) -> syn::Result<()> {
ensure_spanned!(
self.text_signature.is_none(),
text_signature.kw.span() => "`text_signature` may only be specified once"
);
self.text_signature = Some(text_signature);
Ok(())
}
}
pub fn build_py_class(
class: &mut syn::ItemStruct,
attr: &PyClassArgs,
args: &PyClassArgs,
methods_type: PyClassMethodsType,
) -> syn::Result<TokenStream> {
let text_signature = utils::parse_text_signature_attrs(
&mut class.attrs,
&get_class_python_name(&class.ident, attr),
let mut options = PyClassPyO3Options::take_pyo3_options(&mut class.attrs)?;
if let Some(text_signature) =
take_deprecated_text_signature_attribute(&mut class.attrs, &mut options.deprecations)?
{
options.set_text_signature(text_signature)?;
}
let doc = utils::get_doc(
&class.attrs,
options
.text_signature
.as_ref()
.map(|attr| (get_class_python_name(&class.ident, args), attr)),
)?;
let doc = utils::get_doc(&class.attrs, text_signature, true)?;
ensure_spanned!(
class.generics.params.is_empty(),
@ -201,7 +260,14 @@ pub fn build_py_class(
}
};
impl_class(&class.ident, &attr, doc, field_options, methods_type)
impl_class(
&class.ident,
&args,
doc,
field_options,
methods_type,
options.deprecations,
)
}
/// `#[pyo3()]` options for pyclass fields
@ -308,6 +374,7 @@ fn impl_class(
doc: syn::LitStr,
field_options: Vec<(&syn::Field, FieldPyO3Options)>,
methods_type: PyClassMethodsType,
deprecations: Deprecations,
) -> syn::Result<TokenStream> {
let cls_name = get_class_python_name(cls, attr).to_string();
@ -429,6 +496,8 @@ fn impl_class(
#[inline]
fn type_object_raw(py: pyo3::Python) -> *mut pyo3::ffi::PyTypeObject {
#deprecations
use pyo3::type_object::LazyStaticType;
static TYPE_OBJECT: LazyStaticType = LazyStaticType::new();
TYPE_OBJECT.get_or_init::<Self>(py)

View File

@ -2,8 +2,9 @@
use crate::{
attributes::{
self, get_deprecated_name_attribute, get_pyo3_options, take_attributes,
FromPyWithAttribute, NameAttribute,
self, get_deprecated_name_attribute, get_deprecated_text_signature_attribute,
get_pyo3_options, take_attributes, FromPyWithAttribute, NameAttribute,
TextSignatureAttribute,
},
deprecations::Deprecations,
method::{self, FnArg, FnSpec},
@ -211,6 +212,7 @@ pub struct PyFunctionOptions {
pub pass_module: bool,
pub name: Option<NameAttribute>,
pub signature: Option<PyFunctionSignature>,
pub text_signature: Option<TextSignatureAttribute>,
pub deprecations: Deprecations,
}
@ -220,6 +222,7 @@ impl Parse for PyFunctionOptions {
pass_module: false,
name: None,
signature: None,
text_signature: None,
deprecations: Deprecations::new(),
};
@ -228,6 +231,7 @@ impl Parse for PyFunctionOptions {
if lookahead.peek(attributes::kw::name)
|| lookahead.peek(attributes::kw::pass_module)
|| lookahead.peek(attributes::kw::signature)
|| lookahead.peek(attributes::kw::text_signature)
{
options.add_attributes(std::iter::once(input.parse()?))?;
if !input.is_empty() {
@ -250,6 +254,7 @@ pub enum PyFunctionOption {
Name(NameAttribute),
PassModule(attributes::kw::pass_module),
Signature(PyFunctionSignature),
TextSignature(TextSignatureAttribute),
}
impl Parse for PyFunctionOption {
@ -261,6 +266,8 @@ impl Parse for PyFunctionOption {
input.parse().map(PyFunctionOption::PassModule)
} else if lookahead.peek(attributes::kw::signature) {
input.parse().map(PyFunctionOption::Signature)
} else if lookahead.peek(attributes::kw::text_signature) {
input.parse().map(PyFunctionOption::TextSignature)
} else {
Err(lookahead.error())
}
@ -283,6 +290,13 @@ impl PyFunctionOptions {
{
self.set_name(name)?;
Ok(true)
} else if let Some(text_signature) =
get_deprecated_text_signature_attribute(attr, &mut self.deprecations)?
{
self.add_attributes(std::iter::once(PyFunctionOption::TextSignature(
text_signature,
)))?;
Ok(true)
} else {
Ok(false)
}
@ -313,6 +327,13 @@ impl PyFunctionOptions {
);
self.signature = Some(signature);
}
PyFunctionOption::TextSignature(text_signature) => {
ensure_spanned!(
self.text_signature.is_none(),
text_signature.kw.span() => "`text_signature` may only be specified once"
);
self.text_signature = Some(text_signature);
}
}
}
Ok(())
@ -379,8 +400,13 @@ pub fn impl_wrap_pyfunction(
let ty = method::get_return_info(&func.sig.output);
let text_signature = utils::parse_text_signature_attrs(&mut func.attrs, &python_name)?;
let doc = utils::get_doc(&func.attrs, text_signature, true)?;
let doc = utils::get_doc(
&func.attrs,
options
.text_signature
.as_ref()
.map(|attr| (&python_name, attr)),
)?;
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);

View File

@ -876,7 +876,7 @@ impl PropertyType<'_> {
fn doc(&self) -> Cow<syn::LitStr> {
match self {
PropertyType::Descriptor { field, .. } => {
let doc = utils::get_doc(&field.attrs, None, true)
let doc = utils::get_doc(&field.attrs, None)
.unwrap_or_else(|_| syn::LitStr::new("", Span::call_site()));
Cow::Owned(doc)
}

View File

@ -2,6 +2,8 @@
use proc_macro2::Span;
use syn::spanned::Spanned;
use crate::attributes::TextSignatureAttribute;
/// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span.
macro_rules! err_spanned {
($span:expr => $msg:expr) => {
@ -56,79 +58,19 @@ pub fn option_type_argument(ty: &syn::Type) -> Option<&syn::Type> {
None
}
pub fn is_text_signature_attr(attr: &syn::Attribute) -> bool {
attr.path.is_ident("text_signature")
}
fn parse_text_signature_attr(
attr: &syn::Attribute,
python_name: &syn::Ident,
) -> syn::Result<Option<syn::LitStr>> {
if !is_text_signature_attr(attr) {
return Ok(None);
}
let python_name_str = python_name.to_string();
let python_name_str = python_name_str
.rsplit('.')
.next()
.map(str::trim)
.filter(|v| !v.is_empty())
.ok_or_else(|| err_spanned!(python_name.span() => "failed to parse python name"))?;
match attr.parse_meta()? {
syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(lit),
..
}) => {
let value = lit.value();
ensure_spanned!(
value.starts_with('(') && value.ends_with(')'),
lit.span() => "text_signature must start with \"(\" and end with \")\""
);
Ok(Some(syn::LitStr::new(
&(python_name_str.to_owned() + &value),
lit.span(),
)))
}
meta => bail_spanned!(
meta.span() => "text_signature must be of the form #[text_signature = \"\"]"
),
}
}
pub fn parse_text_signature_attrs(
attrs: &mut Vec<syn::Attribute>,
python_name: &syn::Ident,
) -> syn::Result<Option<syn::LitStr>> {
let mut text_signature = None;
let mut attrs_out = Vec::with_capacity(attrs.len());
for attr in attrs.drain(..) {
if let Some(value) = parse_text_signature_attr(&attr, python_name)? {
ensure_spanned!(
text_signature.is_none(),
attr.span() => "text_signature attribute already specified previously"
);
text_signature = Some(value);
} else {
attrs_out.push(attr);
}
}
*attrs = attrs_out;
Ok(text_signature)
}
// FIXME(althonos): not sure the docstring formatting is on par here.
// Returns a null-terminated syn::LitStr for use as a Python docstring.
pub fn get_doc(
attrs: &[syn::Attribute],
text_signature: Option<syn::LitStr>,
null_terminated: bool,
text_signature: Option<(&syn::Ident, &TextSignatureAttribute)>,
) -> syn::Result<syn::LitStr> {
let mut doc = String::new();
let mut span = Span::call_site();
if let Some(text_signature) = text_signature {
if let Some((python_name, text_signature)) = text_signature {
// create special doc string lines to set `__text_signature__`
span = text_signature.span();
doc.push_str(&text_signature.value());
doc.push_str(&python_name.to_string());
span = text_signature.lit.span();
doc.push_str(&text_signature.lit.value());
doc.push_str("\n--\n\n");
}
@ -136,9 +78,12 @@ pub fn get_doc(
let mut first = true;
for attr in attrs.iter() {
if let Ok(syn::Meta::NameValue(metanv)) = attr.parse_meta() {
if metanv.path.is_ident("doc") {
if let syn::Lit::Str(litstr) = metanv.lit {
if attr.path.is_ident("doc") {
match attr.parse_meta()? {
syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(litstr),
..
}) => {
if first {
first = false;
span = litstr.span();
@ -151,16 +96,13 @@ pub fn get_doc(
doc.push_str(&d);
};
separator = "\n";
} else {
bail_spanned!(metanv.span() => "invalid doc comment")
}
_ => bail_spanned!(attr.span() => "invalid doc comment"),
}
}
}
if null_terminated {
doc.push('\0');
}
doc.push('\0');
Ok(syn::LitStr::new(&doc, span))
}

View File

@ -32,7 +32,7 @@ pub fn pymodule(attr: TokenStream, input: TokenStream) -> TokenStream {
return err.to_compile_error().into();
}
let doc = match get_doc(&ast.attrs, None, false) {
let doc = match get_doc(&ast.attrs, None) {
Ok(doc) => doc,
Err(err) => return err.to_compile_error().into(),
};
@ -185,7 +185,7 @@ pub fn pymethods(_: TokenStream, input: TokenStream) -> TokenStream {
/// | [`#[classattr]`][9] | Defines a class variable. |
/// | [`#[args]`][10] | Define a method's default arguments and allows the function to receive `*args` and `**kwargs`. |
///
/// Methods within a `#[pymethods]` block can also be annotated with any of the attributes which can
/// Methods within a `#[pymethods]` block can also be annotated with any of the `#[pyo3]` options which can
/// be used with [`#[pyfunction]`][attr.pyfunction.html].
///
/// For more on creating class methods see the [class section of the guide][1].
@ -212,11 +212,12 @@ pub fn pymethods_with_inventory(_: TokenStream, input: TokenStream) -> TokenStre
/// A proc macro used to expose Rust functions to Python.
///
/// Functions annotated with `#[pyfunction]` can also be annotated with the following:
/// Functions annotated with `#[pyfunction]` can also be annotated with the following `#[pyo3]` options:
///
/// | Annotation | Description |
/// | :- | :- |
/// | `#[pyo3(name = "...")]` | Defines the name of the function in Python. |
/// | `#[pyo3(text_signature = "...")]` | Defines the `__text_signature__` attribute of the function in Python. |
///
/// For more on exposing functions see the [function section of the guide][1].
///

View File

@ -294,7 +294,7 @@ impl ModuleDef {
/// Make new module defenition with given module name.
///
/// # Safety
/// `name` must be a null-terminated string.
/// `name` and `doc` must be null-terminated strings.
pub const unsafe fn new(name: &'static str, doc: &'static str) -> Self {
const INIT: ffi::PyModuleDef = ffi::PyModuleDef {
m_base: ffi::PyModuleDef_HEAD_INIT,

View File

@ -11,3 +11,9 @@ pub const NAME_ATTRIBUTE: () = ();
note = "use `#[pyfn(m)] #[pyo3(name = \"...\")]` instead of `#[pyfn(m, \"...\")]`"
)]
pub const PYFN_NAME_ARGUMENT: () = ();
#[deprecated(
since = "0.14.0",
note = "use `#[pyo3(text_signature = \"...\")]` instead of `#[text_signature = \"...\"]`"
)]
pub const TEXT_SIGNATURE_ATTRIBUTE: () = ();

View File

@ -37,7 +37,7 @@ fn class_with_docs_and_signature() {
/// docs line1
#[pyclass]
/// docs line2
#[text_signature = "(a, b=None, *, c=42)"]
#[pyo3(text_signature = "(a, b=None, *, c=42)")]
/// docs line3
struct MyClass {}
@ -71,7 +71,7 @@ fn class_with_docs_and_signature() {
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
fn class_with_signature() {
#[pyclass]
#[text_signature = "(a, b=None, *, c=42)"]
#[pyo3(text_signature = "(a, b=None, *, c=42)")]
struct MyClass {}
#[pymethods]
@ -103,7 +103,7 @@ fn class_with_signature() {
#[test]
fn test_function() {
#[pyfunction(a, b = "None", "*", c = 42)]
#[text_signature = "(a, b=None, *, c=42)"]
#[pyo3(text_signature = "(a, b=None, *, c=42)")]
fn my_function(a: i32, b: Option<i32>, c: i32) {
let _ = (a, b, c);
}
@ -120,7 +120,7 @@ fn test_pyfn() {
#[pymodule]
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
#[pyfn(m, a, b = "None", "*", c = 42)]
#[text_signature = "(a, b=None, *, c=42)"]
#[pyo3(text_signature = "(a, b=None, *, c=42)")]
fn my_function(a: i32, b: Option<i32>, c: i32) {
let _ = (a, b, c);
}
@ -145,21 +145,21 @@ fn test_methods() {
#[pymethods]
impl MyClass {
#[text_signature = "($self, a)"]
#[pyo3(text_signature = "($self, a)")]
fn method(&self, a: i32) {
let _ = a;
}
#[text_signature = "($self, b)"]
#[pyo3(text_signature = "($self, b)")]
fn pyself_method(_this: &PyCell<Self>, b: i32) {
let _ = b;
}
#[classmethod]
#[text_signature = "($cls, c)"]
#[pyo3(text_signature = "($cls, c)")]
fn class_method(_cls: &PyType, c: i32) {
let _ = c;
}
#[staticmethod]
#[text_signature = "(d)"]
#[pyo3(text_signature = "(d)")]
fn static_method(d: i32) {
let _ = d;
}
@ -195,7 +195,7 @@ fn test_methods() {
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
fn test_raw_identifiers() {
#[pyclass]
#[text_signature = "($self)"]
#[pyo3(text_signature = "($self)")]
struct r#MyClass {}
#[pymethods]
@ -204,7 +204,7 @@ fn test_raw_identifiers() {
fn new() -> MyClass {
MyClass {}
}
#[text_signature = "($self)"]
#[pyo3(text_signature = "($self)")]
fn r#method(&self) {}
}

View File

@ -3,6 +3,7 @@
use pyo3::prelude::*;
#[pyclass]
#[text_signature = "()"]
struct TestClass {
num: u32,
}
@ -14,20 +15,24 @@ impl TestClass {
const DEPRECATED_NAME_CONSTANT: i32 = 0;
#[name = "num"]
#[text_signature = "()"]
fn deprecated_name_pymethod(&self) { }
#[staticmethod]
#[name = "custom_static"]
#[text_signature = "()"]
fn deprecated_name_staticmethod() {}
}
#[pyfunction]
#[name = "foo"]
#[text_signature = "()"]
fn deprecated_name_pyfunction() { }
#[pymodule]
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
#[pyfn(m, "some_name")]
#[text_signature = "()"]
fn deprecated_name_pyfn() { }
Ok(())

View File

@ -1,7 +1,7 @@
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
--> $DIR/deprecations.rs:13:5
--> $DIR/deprecations.rs:14:5
|
13 | #[name = "num"]
14 | #[name = "num"]
| ^
|
note: the lint level is defined here
@ -11,25 +11,55 @@ note: the lint level is defined here
| ^^^^^^^^^^
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
--> $DIR/deprecations.rs:16:5
--> $DIR/deprecations.rs:17:5
|
16 | #[name = "num"]
17 | #[name = "num"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATTRIBUTE`: use `#[pyo3(text_signature = "...")]` instead of `#[text_signature = "..."]`
--> $DIR/deprecations.rs:18:5
|
18 | #[text_signature = "()"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
--> $DIR/deprecations.rs:20:5
--> $DIR/deprecations.rs:22:5
|
20 | #[name = "custom_static"]
22 | #[name = "custom_static"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATTRIBUTE`: use `#[pyo3(text_signature = "...")]` instead of `#[text_signature = "..."]`
--> $DIR/deprecations.rs:23:5
|
23 | #[text_signature = "()"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
--> $DIR/deprecations.rs:25:1
--> $DIR/deprecations.rs:28:1
|
25 | #[name = "foo"]
28 | #[name = "foo"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATTRIBUTE`: use `#[pyo3(text_signature = "...")]` instead of `#[text_signature = "..."]`
--> $DIR/deprecations.rs:29:1
|
29 | #[text_signature = "()"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::PYFN_NAME_ARGUMENT`: use `#[pyfn(m)] #[pyo3(name = "...")]` instead of `#[pyfn(m, "...")]`
--> $DIR/deprecations.rs:30:15
--> $DIR/deprecations.rs:34:15
|
30 | #[pyfn(m, "some_name")]
34 | #[pyfn(m, "some_name")]
| ^^^^^^^^^^^
error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATTRIBUTE`: use `#[pyo3(text_signature = "...")]` instead of `#[text_signature = "..."]`
--> $DIR/deprecations.rs:35:5
|
35 | #[text_signature = "()"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATTRIBUTE`: use `#[pyo3(text_signature = "...")]` instead of `#[text_signature = "..."]`
--> $DIR/deprecations.rs:6:1
|
6 | #[text_signature = "()"]
| ^

View File

@ -48,35 +48,35 @@ impl MyClass {
#[pymethods]
impl MyClass {
#[new]
#[text_signature = "()"]
#[pyo3(text_signature = "()")]
fn text_signature_on_new() {}
}
#[pymethods]
impl MyClass {
#[call]
#[text_signature = "()"]
#[pyo3(text_signature = "()")]
fn text_signature_on_call(&self) {}
}
#[pymethods]
impl MyClass {
#[getter(x)]
#[text_signature = "()"]
#[pyo3(text_signature = "()")]
fn text_signature_on_getter(&self) {}
}
#[pymethods]
impl MyClass {
#[setter(x)]
#[text_signature = "()"]
#[pyo3(text_signature = "()")]
fn text_signature_on_setter(&self) {}
}
#[pymethods]
impl MyClass {
#[classattr]
#[text_signature = "()"]
#[pyo3(text_signature = "()")]
fn text_signature_on_classattr() {}
}

View File

@ -35,34 +35,34 @@ error: expected receiver for #[setter]
| ^^
error: text_signature not allowed on __new__; if you want to add a signature on __new__, put it on the struct definition instead
--> $DIR/invalid_pymethods.rs:51:24
--> $DIR/invalid_pymethods.rs:51:12
|
51 | #[text_signature = "()"]
| ^^^^
51 | #[pyo3(text_signature = "()")]
| ^^^^^^^^^^^^^^
error: text_signature not allowed with this method type
--> $DIR/invalid_pymethods.rs:58:24
--> $DIR/invalid_pymethods.rs:58:12
|
58 | #[text_signature = "()"]
| ^^^^
58 | #[pyo3(text_signature = "()")]
| ^^^^^^^^^^^^^^
error: text_signature not allowed with this method type
--> $DIR/invalid_pymethods.rs:65:24
--> $DIR/invalid_pymethods.rs:65:12
|
65 | #[text_signature = "()"]
| ^^^^
65 | #[pyo3(text_signature = "()")]
| ^^^^^^^^^^^^^^
error: text_signature not allowed with this method type
--> $DIR/invalid_pymethods.rs:72:24
--> $DIR/invalid_pymethods.rs:72:12
|
72 | #[text_signature = "()"]
| ^^^^
72 | #[pyo3(text_signature = "()")]
| ^^^^^^^^^^^^^^
error: text_signature not allowed with this method type
--> $DIR/invalid_pymethods.rs:79:24
--> $DIR/invalid_pymethods.rs:79:12
|
79 | #[text_signature = "()"]
| ^^^^
79 | #[pyo3(text_signature = "()")]
| ^^^^^^^^^^^^^^
error: cannot specify a second method type
--> $DIR/invalid_pymethods.rs:86:7