text_signature: move to `#[pyo3(text_signature = "...")]`
This commit is contained in:
parent
a5810eaffa
commit
cec4c2d2e9
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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) })
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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].
|
||||
///
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: () = ();
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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 = "()"]
|
||||
| ^
|
||||
|
|
|
@ -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() {}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue