Merge branch 'PyO3:main' into num-dependency
This commit is contained in:
commit
d344c0a909
|
@ -0,0 +1,107 @@
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
name: Benchmark
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
name: Cargo benchmark
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: cargo-${{ runner.os }}-bench-${{ hashFiles('**/Cargo.toml') }}
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
for bench in call dict gil list pyclass pyobject set tuple; do
|
||||
cargo bench --features hashbrown --bench "bench_$bench" -- --output-format bencher | tee -a output.txt
|
||||
done
|
||||
|
||||
# Download previous benchmark result from cache (if exists)
|
||||
- name: Download previous benchmark data
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ./cache
|
||||
key: ${{ runner.os }}-benchmark
|
||||
|
||||
# Run `github-action-benchmark` action
|
||||
- name: Store benchmark result
|
||||
uses: rhysd/github-action-benchmark@v1
|
||||
with:
|
||||
name: pyo3-bench
|
||||
# What benchmark tool the output.txt came from
|
||||
tool: 'cargo'
|
||||
# Where the output from the benchmark tool is stored
|
||||
output-file-path: output.txt
|
||||
# # Where the previous data file is stored
|
||||
# external-data-json-path: ./cache/benchmark-data.json
|
||||
# Workflow will fail when an alert happens
|
||||
fail-on-alert: true
|
||||
# GitHub API token to make a commit comment
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Enable alert commit comment
|
||||
comment-on-alert: true
|
||||
alert-comment-cc-users: '@PyO3/pyo3'
|
||||
auto-push: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
pytest-benchmark:
|
||||
name: pytest benchmark
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: cargo-${{ runner.os }}-pytest-bench-${{ hashFiles('**/Cargo.toml') }}
|
||||
continue-on-error: true
|
||||
|
||||
- name: Download previous benchmark data
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ./cache
|
||||
key: ${{ runner.os }}-pytest-benchmark
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
cd examples/pyo3-benchmarks
|
||||
python -m pip install -r requirements-dev.txt
|
||||
python setup.py develop
|
||||
pytest --benchmark-json ../../output.json
|
||||
- name: Store benchmark result
|
||||
uses: rhysd/github-action-benchmark@v1
|
||||
with:
|
||||
name: pytest-bench
|
||||
tool: 'pytest'
|
||||
output-file-path: output.json
|
||||
fail-on-alert: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
comment-on-alert: true
|
||||
alert-comment-cc-users: '@PyO3/pyo3'
|
||||
auto-push: ${{ github.event_name != 'pull_request' }}
|
|
@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Update `num-complex` optional dependency to 0.4. [#1482](https://github.com/PyO3/pyo3/pull/1482)
|
||||
- Extend `hashbrown` optional dependency supported versions to include 0.11. [#1496](https://github.com/PyO3/pyo3/pull/1496)
|
||||
- Support PyPy 3.7. [#1538](https://github.com/PyO3/pyo3/pull/1538)
|
||||
- Try harder to filter sysconfigdata candidates on arch.config
|
||||
|
||||
### Added
|
||||
- Add conversions for `[T; N]` for all `N` on Rust 1.51 and up. [#1128](https://github.com/PyO3/pyo3/pull/1128)
|
||||
|
@ -24,6 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Add `#[pyo3(name = "...")]` syntax for setting Python names. [#1567](https://github.com/PyO3/pyo3/pull/1567)
|
||||
- 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)
|
||||
|
||||
### 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)
|
||||
|
@ -46,6 +48,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Reduce LLVM line counts to improve compilation times. [#1604](https://github.com/PyO3/pyo3/pull/1604)
|
||||
- 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)
|
||||
|
||||
### Removed
|
||||
- Remove deprecated exception names `BaseException` etc. [#1426](https://github.com/PyO3/pyo3/pull/1426)
|
||||
|
@ -61,6 +64,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- `PyModuleDef_INIT` [#1630](https://github.com/PyO3/pyo3/pull/1630)
|
||||
- Remove `__doc__` from module's `__all__`. [#1509](https://github.com/PyO3/pyo3/pull/1509)
|
||||
- Remove `PYO3_CROSS_INCLUDE_DIR` environment variable and the associated C header parsing functionality. [#1521](https://github.com/PyO3/pyo3/pull/1521)
|
||||
- Remove `raw_pycfunction!` macro. [#1619](https://github.com/PyO3/pyo3/pull/1619)
|
||||
|
||||
### Fixed
|
||||
- Remove FFI definition `PyCFunction_ClearFreeList` for Python 3.9 and later. [#1425](https://github.com/PyO3/pyo3/pull/1425)
|
||||
|
|
|
@ -17,6 +17,13 @@ fn iter_tuple(b: &mut Bencher) {
|
|||
});
|
||||
}
|
||||
|
||||
fn tuple_new(b: &mut Bencher) {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
const LEN: usize = 50_000;
|
||||
b.iter(|| PyTuple::new(py, 0..LEN));
|
||||
}
|
||||
|
||||
fn tuple_get_item(b: &mut Bencher) {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
|
@ -32,6 +39,7 @@ fn tuple_get_item(b: &mut Bencher) {
|
|||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("iter_tuple", iter_tuple);
|
||||
c.bench_function("tuple_new", tuple_new);
|
||||
c.bench_function("tuple_get_item", tuple_get_item);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ pub mod dict_iter;
|
|||
pub mod misc;
|
||||
pub mod objstore;
|
||||
pub mod othermod;
|
||||
pub mod path;
|
||||
pub mod pyclass_iter;
|
||||
pub mod subclassing;
|
||||
|
||||
|
@ -17,6 +18,7 @@ use dict_iter::*;
|
|||
use misc::*;
|
||||
use objstore::*;
|
||||
use othermod::*;
|
||||
use path::*;
|
||||
use pyclass_iter::*;
|
||||
use subclassing::*;
|
||||
|
||||
|
@ -28,6 +30,7 @@ fn pyo3_pytests(py: Python, m: &PyModule) -> PyResult<()> {
|
|||
m.add_wrapped(wrap_pymodule!(misc))?;
|
||||
m.add_wrapped(wrap_pymodule!(objstore))?;
|
||||
m.add_wrapped(wrap_pymodule!(othermod))?;
|
||||
m.add_wrapped(wrap_pymodule!(path))?;
|
||||
m.add_wrapped(wrap_pymodule!(pyclass_iter))?;
|
||||
m.add_wrapped(wrap_pymodule!(subclassing))?;
|
||||
|
||||
|
@ -42,6 +45,7 @@ fn pyo3_pytests(py: Python, m: &PyModule) -> PyResult<()> {
|
|||
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.othermod", m.getattr("othermod")?)?;
|
||||
sys_modules.set_item("pyo3_pytests.path", m.getattr("path")?)?;
|
||||
sys_modules.set_item("pyo3_pytests.pyclass_iter", m.getattr("pyclass_iter")?)?;
|
||||
sys_modules.set_item("pyo3_pytests.subclassing", m.getattr("subclassing")?)?;
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
use pyo3::prelude::*;
|
||||
use pyo3::wrap_pyfunction;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[pyfunction]
|
||||
fn make_path() -> PathBuf {
|
||||
Path::new("/root").to_owned()
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn take_pathbuf(path: PathBuf) -> PathBuf {
|
||||
path
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
fn path(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(make_path, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(take_pathbuf, m)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import pathlib
|
||||
|
||||
import pyo3_pytests.path as rpath
|
||||
|
||||
|
||||
def test_make_path():
|
||||
p = rpath.make_path()
|
||||
assert p == "/root"
|
||||
|
||||
|
||||
def test_take_pathbuf():
|
||||
p = "/root"
|
||||
assert rpath.take_pathbuf(p) == p
|
||||
|
||||
|
||||
def test_take_pathlib():
|
||||
p = pathlib.Path("/root")
|
||||
assert rpath.take_pathbuf(p) == str(p)
|
|
@ -349,7 +349,7 @@ struct MyClass {
|
|||
}
|
||||
```
|
||||
|
||||
The above would make the `num` property available for reading and writing from Python code as `self.num`.
|
||||
The above would make the `num` field available for reading and writing as a `self.num` Python property. To expose the property with a different name to the field, specify this alongside the rest of the options, e.g. `#[pyo3(get, set, name = "custom_name")]`.
|
||||
|
||||
Properties can be readonly or writeonly by using just `#[pyo3(get)]` or `#[pyo3(set)]` respectively.
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ The Rust book suggests to [put integration tests inside a `tests/` directory](ht
|
|||
For a PyO3 `extension-module` project where the `crate-type` is set to `"cdylib"` in your `Cargo.toml`,
|
||||
the compiler won't be able to find your crate and will display errors such as `E0432` or `E0463`:
|
||||
|
||||
```
|
||||
```text
|
||||
error[E0432]: unresolved import `my_crate`
|
||||
--> tests/test_my_crate.rs:1:5
|
||||
|
|
||||
|
@ -45,7 +45,7 @@ error[E0432]: unresolved import `my_crate`
|
|||
|
||||
The best solution is to make your crate types include both `rlib` and `cdylib`:
|
||||
|
||||
```
|
||||
```toml
|
||||
# Cargo.toml
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
@ -56,3 +56,87 @@ crate-type = ["cdylib", "rlib"]
|
|||
This is because Ctrl-C raises a SIGINT signal, which is handled by the calling Python process by simply setting a flag to action upon later. This flag isn't checked while Rust code called from Python is executing, only once control returns to the Python interpreter.
|
||||
|
||||
You can give the Python interpreter a chance to process the signal properly by calling `Python::check_signals`. It's good practice to call this function regularly if you have a long-running Rust function so that your users can cancel it.
|
||||
|
||||
## `#[pyo3(get)]` clones my field!
|
||||
|
||||
You may have a nested struct similar to this:
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
#[derive(Clone)]
|
||||
struct Inner { /* fields omitted */ }
|
||||
|
||||
#[pyclass]
|
||||
struct Outer {
|
||||
#[pyo3(get)]
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl Outer {
|
||||
#[new]
|
||||
fn __new__() -> Self {
|
||||
Self { inner: Inner {} }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When Python code accesses `Outer`'s field, PyO3 will return a new object on every access (note that their addresses are different):
|
||||
|
||||
```python
|
||||
outer = Outer()
|
||||
|
||||
a = outer.inner
|
||||
b = outer.inner
|
||||
|
||||
assert a is b, f"a: {a}\nb: {b}"
|
||||
```
|
||||
|
||||
```text
|
||||
AssertionError: a: <builtins.Inner object at 0x00000238FFB9C7B0>
|
||||
b: <builtins.Inner object at 0x00000238FFB9C830>
|
||||
```
|
||||
|
||||
This can be especially confusing if the field is mutable, as getting the field and then mutating it won't persist - you'll just get a fresh clone of the original on the next access. Unfortunately Python and Rust don't agree about ownership - if PyO3 gave out references to (possibly) temporary Rust objects to Python code, Python code could then keep that reference alive indefinitely. Therefore returning Rust objects requires cloning.
|
||||
|
||||
If you don't want that cloning to happen, a workaround is to allocate the field on the Python heap and store a reference to that, by using [`Py<...>`]({{#PYO3_DOCS_URL}}/pyo3/struct.Py.html):
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
#[derive(Clone)]
|
||||
struct Inner { /* fields omitted */ }
|
||||
|
||||
#[pyclass]
|
||||
struct Outer {
|
||||
#[pyo3(get)]
|
||||
inner: Py<Inner>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl Outer {
|
||||
#[new]
|
||||
fn __new__(py: Python) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
inner: Py::new(py, Inner {})?,
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
This time `a` and `b` *are* the same object:
|
||||
```python
|
||||
outer = Outer()
|
||||
|
||||
a = outer.inner
|
||||
b = outer.inner
|
||||
|
||||
assert a is b, f"a: {a}\nb: {b}"
|
||||
print(f"a: {a}\nb: {b}")
|
||||
```
|
||||
|
||||
```text
|
||||
a: <builtins.Inner object at 0x0000020044FCC670>
|
||||
b: <builtins.Inner object at 0x0000020044FCC670>
|
||||
```
|
||||
The downside to this approach is that any Rust code working on the `Outer` struct now has to acquire the GIL to do anything with its field.
|
||||
|
||||
|
|
|
@ -279,12 +279,11 @@ in the function body.
|
|||
|
||||
## Accessing the FFI functions
|
||||
|
||||
In order to make Rust functions callable from Python, PyO3 generates a
|
||||
`extern "C" Fn(slf: *mut PyObject, args: *mut PyObject, kwargs: *mut PyObject) -> *mut Pyobject`
|
||||
function and embeds the call to the Rust function inside this FFI-wrapper function. This
|
||||
wrapper handles extraction of the regular arguments and the keyword arguments from the input
|
||||
`PyObjects`. Since this function is not user-defined but required to build a `PyCFunction`, PyO3
|
||||
offers the `raw_pycfunction!()` macro to get the identifier of this generated wrapper.
|
||||
In order to make Rust functions callable from Python, PyO3 generates an `extern "C"`
|
||||
function whose exact signature depends on the Rust signature. (PyO3 chooses the optimal
|
||||
Python argument passing convention.) It then embeds the call to the Rust function inside this
|
||||
FFI-wrapper function. This wrapper handles extraction of the regular arguments and the keyword
|
||||
arguments from the input `PyObject`s.
|
||||
|
||||
The `wrap_pyfunction` macro can be used to directly get a `PyCFunction` given a
|
||||
`#[pyfunction]` and a `PyModule`: `wrap_pyfunction!(rust_fun, module)`.
|
||||
|
|
|
@ -484,6 +484,24 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
|
|||
};
|
||||
sysconfig_paths.extend(sysc);
|
||||
}
|
||||
// If we got more than one file, only take those that contain the arch name.
|
||||
// For ubuntu 20.04 with host architecture x86_64 and a foreign architecture of armhf
|
||||
// this reduces the number of candidates to 1:
|
||||
//
|
||||
// $ find /usr/lib/python3.8/ -name '_sysconfigdata*.py' -not -lname '*'
|
||||
// /usr/lib/python3.8/_sysconfigdata__x86_64-linux-gnu.py
|
||||
// /usr/lib/python3.8/_sysconfigdata__arm-linux-gnueabihf.py
|
||||
if sysconfig_paths.len() > 1 {
|
||||
let temp = sysconfig_paths
|
||||
.iter()
|
||||
.filter(|p| p.to_string_lossy().contains(&cross.arch))
|
||||
.cloned()
|
||||
.collect::<Vec<PathBuf>>();
|
||||
if !temp.is_empty() {
|
||||
sysconfig_paths = temp;
|
||||
}
|
||||
}
|
||||
|
||||
sysconfig_paths
|
||||
}
|
||||
|
||||
|
|
|
@ -12,9 +12,11 @@ pub mod kw {
|
|||
syn::custom_keyword!(annotation);
|
||||
syn::custom_keyword!(attribute);
|
||||
syn::custom_keyword!(from_py_with);
|
||||
syn::custom_keyword!(get);
|
||||
syn::custom_keyword!(item);
|
||||
syn::custom_keyword!(pass_module);
|
||||
syn::custom_keyword!(name);
|
||||
syn::custom_keyword!(set);
|
||||
syn::custom_keyword!(signature);
|
||||
syn::custom_keyword!(transparent);
|
||||
}
|
||||
|
@ -43,9 +45,7 @@ impl Parse for NameAttribute {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_pyo3_attributes<T: Parse>(
|
||||
attr: &syn::Attribute,
|
||||
) -> Result<Option<Punctuated<T, Comma>>> {
|
||||
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)
|
||||
} else {
|
||||
|
@ -83,6 +83,19 @@ pub fn take_attributes(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn take_pyo3_options<T: Parse>(attrs: &mut Vec<syn::Attribute>) -> Result<Vec<T>> {
|
||||
let mut out = Vec::new();
|
||||
take_attributes(attrs, |attr| {
|
||||
if let Some(options) = get_pyo3_options(attr)? {
|
||||
out.extend(options.into_iter());
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn get_deprecated_name_attribute(
|
||||
attr: &syn::Attribute,
|
||||
deprecations: &mut Deprecations,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::attributes::{self, get_pyo3_attributes, FromPyWithAttribute};
|
||||
use crate::attributes::{self, get_pyo3_options, FromPyWithAttribute};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
|
@ -290,7 +290,7 @@ impl ContainerOptions {
|
|||
annotation: None,
|
||||
};
|
||||
for attr in attrs {
|
||||
if let Some(pyo3_attrs) = get_pyo3_attributes(attr)? {
|
||||
if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
|
||||
for pyo3_attr in pyo3_attrs {
|
||||
match pyo3_attr {
|
||||
ContainerPyO3Attribute::Transparent(kw) => {
|
||||
|
@ -388,7 +388,7 @@ impl FieldPyO3Attributes {
|
|||
let mut from_py_with = None;
|
||||
|
||||
for attr in attrs {
|
||||
if let Some(pyo3_attrs) = get_pyo3_attributes(attr)? {
|
||||
if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
|
||||
for pyo3_attr in pyo3_attrs {
|
||||
match pyo3_attr {
|
||||
FieldPyO3Attribute::Getter(field_getter) => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
attributes::{
|
||||
self, get_deprecated_name_attribute, get_pyo3_attributes, is_attribute_ident,
|
||||
take_attributes, NameAttribute,
|
||||
self, get_deprecated_name_attribute, get_pyo3_options, is_attribute_ident, take_attributes,
|
||||
NameAttribute,
|
||||
},
|
||||
deprecations::Deprecations,
|
||||
};
|
||||
|
@ -69,7 +69,7 @@ impl ConstAttributes {
|
|||
);
|
||||
attributes.is_class_attr = true;
|
||||
Ok(true)
|
||||
} else if let Some(pyo3_attributes) = get_pyo3_attributes(attr)? {
|
||||
} else if let Some(pyo3_attributes) = get_pyo3_options(attr)? {
|
||||
for pyo3_attr in pyo3_attributes {
|
||||
match pyo3_attr {
|
||||
PyO3ConstAttribute::Name(name) => attributes.set_name(name)?,
|
||||
|
|
|
@ -158,6 +158,28 @@ pub fn parse_method_receiver(arg: &syn::FnArg) -> syn::Result<SelfType> {
|
|||
}
|
||||
|
||||
impl<'a> FnSpec<'a> {
|
||||
/// Determine if the function gets passed a *args tuple or **kwargs dict.
|
||||
pub fn accept_args_kwargs(&self) -> (bool, bool) {
|
||||
let (mut accept_args, mut accept_kwargs) = (false, false);
|
||||
|
||||
for s in &self.attrs {
|
||||
match s {
|
||||
Argument::VarArgs(_) => accept_args = true,
|
||||
Argument::KeywordArgs(_) => accept_kwargs = true,
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
(accept_args, accept_kwargs)
|
||||
}
|
||||
|
||||
/// Return true if the function can use METH_FASTCALL.
|
||||
///
|
||||
/// This is true on Py3.7+, except with the stable ABI (abi3).
|
||||
pub fn can_use_fastcall(&self) -> bool {
|
||||
cfg!(all(Py_3_7, not(Py_LIMITED_API)))
|
||||
}
|
||||
|
||||
/// Parser function signature and function attributes
|
||||
pub fn parse(
|
||||
sig: &'a mut syn::Signature,
|
||||
|
@ -220,9 +242,8 @@ impl<'a> FnSpec<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn null_terminated_python_name(&self) -> TokenStream {
|
||||
let name = format!("{}\0", self.python_name);
|
||||
quote!({#name})
|
||||
pub fn null_terminated_python_name(&self) -> syn::LitStr {
|
||||
syn::LitStr::new(&format!("{}\0", self.python_name), self.python_name.span())
|
||||
}
|
||||
|
||||
fn parse_text_signature(
|
||||
|
|
|
@ -114,7 +114,7 @@ fn get_pyfn_attr(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<PyFnArgs
|
|||
})?;
|
||||
|
||||
if let Some(pyfn_args) = &mut pyfn_args {
|
||||
pyfn_args.options.take_pyo3_attributes(attrs)?;
|
||||
pyfn_args.options.take_pyo3_options(attrs)?;
|
||||
}
|
||||
|
||||
Ok(pyfn_args)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use crate::method::{FnType, SelfType};
|
||||
use crate::attributes::{self, take_pyo3_options, NameAttribute};
|
||||
use crate::pyimpl::PyClassMethodsType;
|
||||
use crate::pymethod::{impl_py_getter_def, impl_py_setter_def, PropertyType};
|
||||
use crate::utils;
|
||||
|
@ -9,7 +9,7 @@ use quote::quote;
|
|||
use syn::ext::IdentExt;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{parse_quote, spanned::Spanned, Expr, Token};
|
||||
use syn::{parse_quote, spanned::Spanned, Expr, Result, Token};
|
||||
|
||||
/// The parsed arguments of the pyclass macro
|
||||
pub struct PyClassArgs {
|
||||
|
@ -26,7 +26,7 @@ pub struct PyClassArgs {
|
|||
}
|
||||
|
||||
impl Parse for PyClassArgs {
|
||||
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut slf = PyClassArgs::default();
|
||||
|
||||
let vars = Punctuated::<Expr, Token![,]>::parse_terminated(input)?;
|
||||
|
@ -57,7 +57,7 @@ impl Default for PyClassArgs {
|
|||
impl PyClassArgs {
|
||||
/// Adda single expression from the comma separated list in the attribute, which is
|
||||
/// either a single word or an assignment expression
|
||||
fn add_expr(&mut self, expr: &Expr) -> syn::parse::Result<()> {
|
||||
fn add_expr(&mut self, expr: &Expr) -> Result<()> {
|
||||
match expr {
|
||||
syn::Expr::Path(exp) if exp.path.segments.len() == 1 => self.add_path(exp),
|
||||
syn::Expr::Assign(assign) => self.add_assign(assign),
|
||||
|
@ -172,63 +172,102 @@ pub fn build_py_class(
|
|||
&get_class_python_name(&class.ident, attr),
|
||||
)?;
|
||||
let doc = utils::get_doc(&class.attrs, text_signature, true)?;
|
||||
let mut descriptors = Vec::new();
|
||||
|
||||
ensure_spanned!(
|
||||
class.generics.params.is_empty(),
|
||||
class.generics.span() => "#[pyclass] cannot have generic parameters"
|
||||
);
|
||||
|
||||
match &mut class.fields {
|
||||
syn::Fields::Named(fields) => {
|
||||
for field in fields.named.iter_mut() {
|
||||
let field_descs = parse_descriptors(field)?;
|
||||
if !field_descs.is_empty() {
|
||||
descriptors.push((field.clone(), field_descs));
|
||||
}
|
||||
}
|
||||
let field_options = match &mut class.fields {
|
||||
syn::Fields::Named(fields) => fields
|
||||
.named
|
||||
.iter_mut()
|
||||
.map(|field| {
|
||||
FieldPyO3Options::take_pyo3_options(&mut field.attrs)
|
||||
.map(move |options| (&*field, options))
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
syn::Fields::Unnamed(fields) => fields
|
||||
.unnamed
|
||||
.iter_mut()
|
||||
.map(|field| {
|
||||
FieldPyO3Options::take_pyo3_options(&mut field.attrs)
|
||||
.map(move |options| (&*field, options))
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
syn::Fields::Unit => {
|
||||
// No fields for unit struct
|
||||
Vec::new()
|
||||
}
|
||||
syn::Fields::Unnamed(fields) => {
|
||||
for field in fields.unnamed.iter_mut() {
|
||||
let field_descs = parse_descriptors(field)?;
|
||||
if !field_descs.is_empty() {
|
||||
descriptors.push((field.clone(), field_descs));
|
||||
}
|
||||
}
|
||||
}
|
||||
syn::Fields::Unit => { /* No fields for unit struct */ }
|
||||
}
|
||||
};
|
||||
|
||||
impl_class(&class.ident, &attr, doc, descriptors, methods_type)
|
||||
impl_class(&class.ident, &attr, doc, field_options, methods_type)
|
||||
}
|
||||
|
||||
/// Parses `#[pyo3(get, set)]`
|
||||
fn parse_descriptors(item: &mut syn::Field) -> syn::Result<Vec<FnType>> {
|
||||
let mut descs = Vec::new();
|
||||
let mut new_attrs = Vec::new();
|
||||
for attr in item.attrs.drain(..) {
|
||||
if let Ok(syn::Meta::List(list)) = attr.parse_meta() {
|
||||
if list.path.is_ident("pyo3") {
|
||||
for meta in list.nested.iter() {
|
||||
if let syn::NestedMeta::Meta(metaitem) = meta {
|
||||
if metaitem.path().is_ident("get") {
|
||||
descs.push(FnType::Getter(SelfType::Receiver { mutable: false }));
|
||||
} else if metaitem.path().is_ident("set") {
|
||||
descs.push(FnType::Setter(SelfType::Receiver { mutable: true }));
|
||||
} else {
|
||||
bail_spanned!(metaitem.span() => "only get and set are supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new_attrs.push(attr)
|
||||
}
|
||||
/// `#[pyo3()]` options for pyclass fields
|
||||
struct FieldPyO3Options {
|
||||
get: bool,
|
||||
set: bool,
|
||||
name: Option<NameAttribute>,
|
||||
}
|
||||
|
||||
enum FieldPyO3Option {
|
||||
Get(attributes::kw::get),
|
||||
Set(attributes::kw::set),
|
||||
Name(NameAttribute),
|
||||
}
|
||||
|
||||
impl Parse for FieldPyO3Option {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::get) {
|
||||
input.parse().map(FieldPyO3Option::Get)
|
||||
} else if lookahead.peek(attributes::kw::set) {
|
||||
input.parse().map(FieldPyO3Option::Set)
|
||||
} else if lookahead.peek(attributes::kw::name) {
|
||||
input.parse().map(FieldPyO3Option::Name)
|
||||
} else {
|
||||
new_attrs.push(attr);
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
item.attrs = new_attrs;
|
||||
Ok(descs)
|
||||
}
|
||||
|
||||
impl FieldPyO3Options {
|
||||
fn take_pyo3_options(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
|
||||
let mut options = FieldPyO3Options {
|
||||
get: false,
|
||||
set: false,
|
||||
name: None,
|
||||
};
|
||||
|
||||
for option in take_pyo3_options(attrs)? {
|
||||
match option {
|
||||
FieldPyO3Option::Get(kw) => {
|
||||
ensure_spanned!(
|
||||
!options.get,
|
||||
kw.span() => "`get` may only be specified once"
|
||||
);
|
||||
options.get = true;
|
||||
}
|
||||
FieldPyO3Option::Set(kw) => {
|
||||
ensure_spanned!(
|
||||
!options.set,
|
||||
kw.span() => "`set` may only be specified once"
|
||||
);
|
||||
options.set = true;
|
||||
}
|
||||
FieldPyO3Option::Name(name) => {
|
||||
ensure_spanned!(
|
||||
options.name.is_none(),
|
||||
name.0.span() => "`name` may only be specified once"
|
||||
);
|
||||
options.name = Some(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
}
|
||||
|
||||
/// To allow multiple #[pymethods] block, we define inventory types.
|
||||
|
@ -267,12 +306,12 @@ fn impl_class(
|
|||
cls: &syn::Ident,
|
||||
attr: &PyClassArgs,
|
||||
doc: syn::LitStr,
|
||||
descriptors: Vec<(syn::Field, Vec<FnType>)>,
|
||||
field_options: Vec<(&syn::Field, FieldPyO3Options)>,
|
||||
methods_type: PyClassMethodsType,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let cls_name = get_class_python_name(cls, attr).to_string();
|
||||
|
||||
let extra = {
|
||||
let alloc = {
|
||||
if let Some(freelist) = &attr.freelist {
|
||||
quote! {
|
||||
impl pyo3::freelist::PyClassWithFreeList for #cls {
|
||||
|
@ -296,17 +335,7 @@ fn impl_class(
|
|||
}
|
||||
};
|
||||
|
||||
let extra = if !descriptors.is_empty() {
|
||||
let path = syn::Path::from(syn::PathSegment::from(cls.clone()));
|
||||
let ty = syn::Type::from(syn::TypePath { path, qself: None });
|
||||
let desc_impls = impl_descriptors(&ty, descriptors)?;
|
||||
quote! {
|
||||
#desc_impls
|
||||
#extra
|
||||
}
|
||||
} else {
|
||||
extra
|
||||
};
|
||||
let descriptors = impl_descriptors(cls, field_options)?;
|
||||
|
||||
// insert space for weak ref
|
||||
let weakref = if attr.has_weaklist {
|
||||
|
@ -481,39 +510,50 @@ fn impl_class(
|
|||
}
|
||||
}
|
||||
|
||||
#extra
|
||||
#alloc
|
||||
|
||||
#descriptors
|
||||
|
||||
#gc_impl
|
||||
})
|
||||
}
|
||||
|
||||
fn impl_descriptors(
|
||||
cls: &syn::Type,
|
||||
descriptors: Vec<(syn::Field, Vec<FnType>)>,
|
||||
cls: &syn::Ident,
|
||||
field_options: Vec<(&syn::Field, FieldPyO3Options)>,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let py_methods: Vec<TokenStream> = descriptors
|
||||
.iter()
|
||||
.flat_map(|(field, fns)| {
|
||||
fns.iter()
|
||||
.map(|desc| {
|
||||
let doc = utils::get_doc(&field.attrs, None, true)
|
||||
.unwrap_or_else(|_| syn::LitStr::new("", Span::call_site()));
|
||||
let property_type = PropertyType::Descriptor(
|
||||
field.ident.as_ref().ok_or_else(
|
||||
|| err_spanned!(field.span() => "`#[pyo3(get, set)]` is not supported on tuple struct fields")
|
||||
)?
|
||||
);
|
||||
match desc {
|
||||
FnType::Getter(self_ty) => {
|
||||
impl_py_getter_def(cls, property_type, self_ty, &doc, &Default::default())
|
||||
}
|
||||
FnType::Setter(self_ty) => {
|
||||
impl_py_setter_def(cls, property_type, self_ty, &doc, &Default::default())
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<syn::Result<TokenStream>>>()
|
||||
let ty = syn::parse_quote!(#cls);
|
||||
let py_methods: Vec<TokenStream> = field_options
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.flat_map(|(field_index, (field, options))| {
|
||||
let name_err = if options.name.is_some() && !options.get && !options.set {
|
||||
Some(Err(err_spanned!(options.name.as_ref().unwrap().0.span() => "`name` is useless without `get` or `set`")))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let getter = if options.get {
|
||||
Some(impl_py_getter_def(&ty, PropertyType::Descriptor {
|
||||
field_index,
|
||||
field,
|
||||
python_name: options.name.as_ref()
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let setter = if options.set {
|
||||
Some(impl_py_setter_def(&ty, PropertyType::Descriptor {
|
||||
field_index,
|
||||
field,
|
||||
python_name: options.name.as_ref()
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
name_err.into_iter().chain(getter).chain(setter)
|
||||
})
|
||||
.collect::<syn::Result<_>>()?;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use crate::{
|
||||
attributes::{
|
||||
self, get_deprecated_name_attribute, get_pyo3_attributes, take_attributes,
|
||||
self, get_deprecated_name_attribute, get_pyo3_options, take_attributes,
|
||||
FromPyWithAttribute, NameAttribute,
|
||||
},
|
||||
deprecations::Deprecations,
|
||||
|
@ -62,7 +62,7 @@ impl PyFunctionArgPyO3Attributes {
|
|||
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
|
||||
let mut attributes = PyFunctionArgPyO3Attributes { from_py_with: None };
|
||||
take_attributes(attrs, |attr| {
|
||||
if let Some(pyo3_attrs) = get_pyo3_attributes(attr)? {
|
||||
if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
|
||||
for attr in pyo3_attrs {
|
||||
match attr {
|
||||
PyFunctionArgPyO3Attribute::FromPyWith(from_py_with) => {
|
||||
|
@ -270,13 +270,13 @@ impl Parse for PyFunctionOption {
|
|||
impl PyFunctionOptions {
|
||||
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
|
||||
let mut options = PyFunctionOptions::default();
|
||||
options.take_pyo3_attributes(attrs)?;
|
||||
options.take_pyo3_options(attrs)?;
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
pub fn take_pyo3_attributes(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
|
||||
pub fn take_pyo3_options(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
|
||||
take_attributes(attrs, |attr| {
|
||||
if let Some(pyo3_attributes) = get_pyo3_attributes(attr)? {
|
||||
if let Some(pyo3_attributes) = get_pyo3_options(attr)? {
|
||||
self.add_attributes(pyo3_attributes)?;
|
||||
Ok(true)
|
||||
} else if let Some(name) = get_deprecated_name_attribute(attr, &mut self.deprecations)?
|
||||
|
@ -332,7 +332,7 @@ pub fn build_py_function(
|
|||
ast: &mut syn::ItemFn,
|
||||
mut options: PyFunctionOptions,
|
||||
) -> syn::Result<TokenStream> {
|
||||
options.take_pyo3_attributes(&mut ast.attrs)?;
|
||||
options.take_pyo3_options(&mut ast.attrs)?;
|
||||
Ok(impl_wrap_pyfunction(ast, options)?.1)
|
||||
}
|
||||
|
||||
|
@ -401,25 +401,29 @@ pub fn impl_wrap_pyfunction(
|
|||
let name = &func.sig.ident;
|
||||
let wrapper_ident = format_ident!("__pyo3_raw_{}", name);
|
||||
let wrapper = function_c_wrapper(name, &wrapper_ident, &spec, options.pass_module)?;
|
||||
let methoddef = if spec.args.is_empty() {
|
||||
quote!(noargs)
|
||||
let (methoddef_meth, cfunc_variant) = if spec.args.is_empty() {
|
||||
(quote!(noargs), quote!(PyCFunction))
|
||||
} else if spec.can_use_fastcall() {
|
||||
(
|
||||
quote!(fastcall_cfunction_with_keywords),
|
||||
quote!(PyCFunctionFastWithKeywords),
|
||||
)
|
||||
} else {
|
||||
quote!(cfunction_with_keywords)
|
||||
};
|
||||
let cfunc = if spec.args.is_empty() {
|
||||
quote!(PyCFunction)
|
||||
} else {
|
||||
quote!(PyCFunctionWithKeywords)
|
||||
(
|
||||
quote!(cfunction_with_keywords),
|
||||
quote!(PyCFunctionWithKeywords),
|
||||
)
|
||||
};
|
||||
|
||||
let wrapped_pyfunction = quote! {
|
||||
#wrapper
|
||||
pub(crate) fn #function_wrapper_ident<'a>(
|
||||
args: impl Into<pyo3::derive_utils::PyFunctionArguments<'a>>
|
||||
) -> pyo3::PyResult<&'a pyo3::types::PyCFunction> {
|
||||
pyo3::types::PyCFunction::internal_new(
|
||||
pyo3::class::methods::PyMethodDef:: #methoddef (
|
||||
pyo3::class::methods::PyMethodDef:: #methoddef_meth (
|
||||
#python_name,
|
||||
pyo3::class::methods:: #cfunc (#wrapper_ident),
|
||||
pyo3::class::methods:: #cfunc_variant (#wrapper_ident),
|
||||
#doc,
|
||||
),
|
||||
args.into(),
|
||||
|
@ -469,8 +473,36 @@ fn function_c_wrapper(
|
|||
})
|
||||
}
|
||||
})
|
||||
} else if spec.can_use_fastcall() {
|
||||
let body = impl_arg_params(spec, None, cb, &py, true)?;
|
||||
Ok(quote! {
|
||||
unsafe extern "C" fn #wrapper_ident(
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_args: *const *mut pyo3::ffi::PyObject,
|
||||
_nargs: pyo3::ffi::Py_ssize_t,
|
||||
_kwnames: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
pyo3::callback::handle_panic(|#py| {
|
||||
#slf_module
|
||||
// _nargs is the number of positional arguments in the _args array,
|
||||
// the number of KW args is given by the length of _kwnames
|
||||
let _kwnames: Option<&pyo3::types::PyTuple> = #py.from_borrowed_ptr_or_opt(_kwnames);
|
||||
// Safety: &PyAny has the same memory layout as `*mut ffi::PyObject`
|
||||
let _args = _args as *const &pyo3::PyAny;
|
||||
let _kwargs = if let Some(kwnames) = _kwnames {
|
||||
std::slice::from_raw_parts(_args.offset(_nargs), kwnames.len())
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
let _args = std::slice::from_raw_parts(_args, _nargs as usize);
|
||||
|
||||
#body
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
let body = impl_arg_params(spec, None, cb, &py)?;
|
||||
let body = impl_arg_params(spec, None, cb, &py, false)?;
|
||||
Ok(quote! {
|
||||
unsafe extern "C" fn #wrapper_ident(
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
|
@ -482,7 +514,6 @@ fn function_c_wrapper(
|
|||
#slf_module
|
||||
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
|
||||
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
|
||||
|
||||
#body
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use crate::attributes::NameAttribute;
|
||||
use crate::utils::ensure_not_async_fn;
|
||||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
use crate::{attributes::FromPyWithAttribute, konst::ConstSpec};
|
||||
|
@ -10,12 +13,6 @@ use proc_macro2::{Span, TokenStream};
|
|||
use quote::{quote, quote_spanned};
|
||||
use syn::{ext::IdentExt, spanned::Spanned, Result};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum PropertyType<'a> {
|
||||
Descriptor(&'a syn::Ident),
|
||||
Function(&'a FnSpec<'a>),
|
||||
}
|
||||
|
||||
pub enum GeneratedPyMethod {
|
||||
Method(TokenStream),
|
||||
New(TokenStream),
|
||||
|
@ -45,19 +42,19 @@ pub fn gen_py_method(
|
|||
FnType::ClassAttribute => {
|
||||
GeneratedPyMethod::Method(impl_py_method_class_attribute(cls, &spec))
|
||||
}
|
||||
FnType::Getter(self_ty) => GeneratedPyMethod::Method(impl_py_getter_def(
|
||||
FnType::Getter(self_type) => GeneratedPyMethod::Method(impl_py_getter_def(
|
||||
cls,
|
||||
PropertyType::Function(&spec),
|
||||
self_ty,
|
||||
&spec.doc,
|
||||
&spec.deprecations,
|
||||
PropertyType::Function {
|
||||
self_type,
|
||||
spec: &spec,
|
||||
},
|
||||
)?),
|
||||
FnType::Setter(self_ty) => GeneratedPyMethod::Method(impl_py_setter_def(
|
||||
FnType::Setter(self_type) => GeneratedPyMethod::Method(impl_py_setter_def(
|
||||
cls,
|
||||
PropertyType::Function(&spec),
|
||||
self_ty,
|
||||
&spec.doc,
|
||||
&spec.deprecations,
|
||||
PropertyType::Function {
|
||||
self_type,
|
||||
spec: &spec,
|
||||
},
|
||||
)?),
|
||||
})
|
||||
}
|
||||
|
@ -96,7 +93,7 @@ pub fn impl_wrap_cfunction_with_keywords(
|
|||
let body = impl_call(cls, &spec);
|
||||
let slf = self_ty.receiver(cls);
|
||||
let py = syn::Ident::new("_py", Span::call_site());
|
||||
let body = impl_arg_params(&spec, Some(cls), body, &py)?;
|
||||
let body = impl_arg_params(&spec, Some(cls), body, &py, false)?;
|
||||
let deprecations = &spec.deprecations;
|
||||
Ok(quote! {{
|
||||
unsafe extern "C" fn __wrap(
|
||||
|
@ -117,6 +114,42 @@ pub fn impl_wrap_cfunction_with_keywords(
|
|||
}})
|
||||
}
|
||||
|
||||
/// Generate function wrapper for PyCFunctionFastWithKeywords
|
||||
pub fn impl_wrap_fastcall_cfunction_with_keywords(
|
||||
cls: &syn::Type,
|
||||
spec: &FnSpec<'_>,
|
||||
self_ty: &SelfType,
|
||||
) -> Result<TokenStream> {
|
||||
let body = impl_call(cls, &spec);
|
||||
let slf = self_ty.receiver(cls);
|
||||
let py = syn::Ident::new("_py", Span::call_site());
|
||||
let body = impl_arg_params(&spec, Some(cls), body, &py, true)?;
|
||||
Ok(quote! {{
|
||||
unsafe extern "C" fn __wrap(
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_args: *const *mut pyo3::ffi::PyObject,
|
||||
_nargs: pyo3::ffi::Py_ssize_t,
|
||||
_kwnames: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
pyo3::callback::handle_panic(|#py| {
|
||||
#slf
|
||||
let _kwnames: Option<&pyo3::types::PyTuple> = #py.from_borrowed_ptr_or_opt(_kwnames);
|
||||
// Safety: &PyAny has the same memory layout as `*mut ffi::PyObject`
|
||||
let _args = _args as *const &pyo3::PyAny;
|
||||
let _kwargs = if let Some(kwnames) = _kwnames {
|
||||
std::slice::from_raw_parts(_args.offset(_nargs), kwnames.len())
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
let _args = std::slice::from_raw_parts(_args, _nargs as usize);
|
||||
|
||||
#body
|
||||
})
|
||||
}
|
||||
__wrap
|
||||
}})
|
||||
}
|
||||
|
||||
/// Generate function wrapper PyCFunction
|
||||
pub fn impl_wrap_noargs(cls: &syn::Type, spec: &FnSpec<'_>, self_ty: &SelfType) -> TokenStream {
|
||||
let body = impl_call(cls, &spec);
|
||||
|
@ -145,7 +178,7 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStream>
|
|||
let names: Vec<syn::Ident> = get_arg_names(&spec);
|
||||
let cb = quote! { #cls::#name(#(#names),*) };
|
||||
let py = syn::Ident::new("_py", Span::call_site());
|
||||
let body = impl_arg_params(spec, Some(cls), cb, &py)?;
|
||||
let body = impl_arg_params(spec, Some(cls), cb, &py, false)?;
|
||||
let deprecations = &spec.deprecations;
|
||||
Ok(quote! {{
|
||||
#[allow(unused_mut)]
|
||||
|
@ -175,7 +208,7 @@ pub fn impl_wrap_class(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStream
|
|||
let names: Vec<syn::Ident> = get_arg_names(&spec);
|
||||
let cb = quote! { pyo3::callback::convert(_py, #cls::#name(&_cls, #(#names),*)) };
|
||||
let py = syn::Ident::new("_py", Span::call_site());
|
||||
let body = impl_arg_params(spec, Some(cls), cb, &py)?;
|
||||
let body = impl_arg_params(spec, Some(cls), cb, &py, false)?;
|
||||
let deprecations = &spec.deprecations;
|
||||
Ok(quote! {{
|
||||
#[allow(unused_mut)]
|
||||
|
@ -203,7 +236,7 @@ pub fn impl_wrap_static(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStrea
|
|||
let names: Vec<syn::Ident> = get_arg_names(&spec);
|
||||
let cb = quote! { pyo3::callback::convert(_py, #cls::#name(#(#names),*)) };
|
||||
let py = syn::Ident::new("_py", Span::call_site());
|
||||
let body = impl_arg_params(spec, Some(cls), cb, &py)?;
|
||||
let body = impl_arg_params(spec, Some(cls), cb, &py, false)?;
|
||||
let deprecations = &spec.deprecations;
|
||||
Ok(quote! {{
|
||||
#[allow(unused_mut)]
|
||||
|
@ -260,17 +293,30 @@ fn impl_call_getter(cls: &syn::Type, spec: &FnSpec) -> syn::Result<TokenStream>
|
|||
/// Generate a function wrapper called `__wrap` for a property getter
|
||||
pub(crate) fn impl_wrap_getter(
|
||||
cls: &syn::Type,
|
||||
property_type: PropertyType,
|
||||
self_ty: &SelfType,
|
||||
property_type: &PropertyType,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let getter_impl = match &property_type {
|
||||
PropertyType::Descriptor(ident) => {
|
||||
let getter_impl = match property_type {
|
||||
PropertyType::Descriptor {
|
||||
field: syn::Field {
|
||||
ident: Some(ident), ..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
// named struct field
|
||||
quote!(_slf.#ident.clone())
|
||||
}
|
||||
PropertyType::Function(spec) => impl_call_getter(cls, spec)?,
|
||||
PropertyType::Descriptor { field_index, .. } => {
|
||||
// tuple struct field
|
||||
let index = syn::Index::from(*field_index);
|
||||
quote!(_slf.#index.clone())
|
||||
}
|
||||
PropertyType::Function { spec, .. } => impl_call_getter(cls, spec)?,
|
||||
};
|
||||
|
||||
let slf = self_ty.receiver(cls);
|
||||
let slf = match property_type {
|
||||
PropertyType::Descriptor { .. } => SelfType::Receiver { mutable: false }.receiver(cls),
|
||||
PropertyType::Function { self_type, .. } => self_type.receiver(cls),
|
||||
};
|
||||
Ok(quote! {{
|
||||
unsafe extern "C" fn __wrap(
|
||||
_slf: *mut pyo3::ffi::PyObject, _: *mut std::os::raw::c_void) -> *mut pyo3::ffi::PyObject
|
||||
|
@ -309,17 +355,30 @@ fn impl_call_setter(cls: &syn::Type, spec: &FnSpec) -> syn::Result<TokenStream>
|
|||
/// Generate a function wrapper called `__wrap` for a property setter
|
||||
pub(crate) fn impl_wrap_setter(
|
||||
cls: &syn::Type,
|
||||
property_type: PropertyType,
|
||||
self_ty: &SelfType,
|
||||
property_type: &PropertyType,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let setter_impl = match &property_type {
|
||||
PropertyType::Descriptor(ident) => {
|
||||
let setter_impl = match property_type {
|
||||
PropertyType::Descriptor {
|
||||
field: syn::Field {
|
||||
ident: Some(ident), ..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
// named struct field
|
||||
quote!({ _slf.#ident = _val; })
|
||||
}
|
||||
PropertyType::Function(spec) => impl_call_setter(cls, spec)?,
|
||||
PropertyType::Descriptor { field_index, .. } => {
|
||||
// tuple struct field
|
||||
let index = syn::Index::from(*field_index);
|
||||
quote!({ _slf.#index = _val; })
|
||||
}
|
||||
PropertyType::Function { spec, .. } => impl_call_setter(cls, spec)?,
|
||||
};
|
||||
|
||||
let slf = self_ty.receiver(cls);
|
||||
let slf = match property_type {
|
||||
PropertyType::Descriptor { .. } => SelfType::Receiver { mutable: true }.receiver(cls),
|
||||
PropertyType::Function { self_type, .. } => self_type.receiver(cls),
|
||||
};
|
||||
Ok(quote! {{
|
||||
#[allow(unused_mut)]
|
||||
unsafe extern "C" fn __wrap(
|
||||
|
@ -356,6 +415,7 @@ pub fn impl_arg_params(
|
|||
self_: Option<&syn::Type>,
|
||||
body: TokenStream,
|
||||
py: &syn::Ident,
|
||||
fastcall: bool,
|
||||
) -> Result<TokenStream> {
|
||||
if spec.args.is_empty() {
|
||||
return Ok(body);
|
||||
|
@ -405,16 +465,7 @@ pub fn impl_arg_params(
|
|||
)?);
|
||||
}
|
||||
|
||||
let (mut accept_args, mut accept_kwargs) = (false, false);
|
||||
|
||||
for s in spec.attrs.iter() {
|
||||
use crate::pyfunction::Argument;
|
||||
match s {
|
||||
Argument::VarArgs(_) => accept_args = true,
|
||||
Argument::KeywordArgs(_) => accept_kwargs = true,
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
let (accept_args, accept_kwargs) = spec.accept_args_kwargs();
|
||||
|
||||
let cls_name = if let Some(cls) = self_ {
|
||||
quote! { Some(<#cls as pyo3::type_object::PyTypeInfo>::NAME) }
|
||||
|
@ -423,6 +474,24 @@ pub fn impl_arg_params(
|
|||
};
|
||||
let python_name = &spec.python_name;
|
||||
|
||||
let (args_to_extract, kwargs_to_extract) = if fastcall {
|
||||
// _args is a &[&PyAny], _kwnames is a Option<&PyTuple> containing the
|
||||
// keyword names of the keyword args in _kwargs
|
||||
(
|
||||
// need copied() for &&PyAny -> &PyAny
|
||||
quote! { _args.iter().copied() },
|
||||
quote! { _kwnames.map(|kwnames| {
|
||||
kwnames.as_slice().iter().copied().zip(_kwargs.iter().copied())
|
||||
}) },
|
||||
)
|
||||
} else {
|
||||
// _args is a &PyTuple, _kwargs is an Option<&PyDict>
|
||||
(
|
||||
quote! { _args.iter() },
|
||||
quote! { _kwargs.map(|dict| dict.iter()) },
|
||||
)
|
||||
};
|
||||
|
||||
// create array of arguments, and then parse
|
||||
Ok(quote! {
|
||||
{
|
||||
|
@ -439,7 +508,12 @@ pub fn impl_arg_params(
|
|||
};
|
||||
|
||||
let mut #args_array = [None; #num_params];
|
||||
let (_args, _kwargs) = DESCRIPTION.extract_arguments(_args, _kwargs, &mut #args_array)?;
|
||||
let (_args, _kwargs) = DESCRIPTION.extract_arguments(
|
||||
#py,
|
||||
#args_to_extract,
|
||||
#kwargs_to_extract,
|
||||
&mut #args_array
|
||||
)?;
|
||||
|
||||
#(#param_conversion)*
|
||||
|
||||
|
@ -593,32 +667,36 @@ pub fn impl_py_method_def(
|
|||
let add_flags = flags.map(|flags| quote!(.flags(#flags)));
|
||||
let python_name = spec.null_terminated_python_name();
|
||||
let doc = &spec.doc;
|
||||
if spec.args.is_empty() {
|
||||
let wrapper = impl_wrap_noargs(cls, spec, self_ty);
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Method({
|
||||
pyo3::class::PyMethodDef::noargs(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyCFunction(#wrapper),
|
||||
#doc
|
||||
)
|
||||
#add_flags
|
||||
|
||||
})
|
||||
})
|
||||
let (methoddef_meth, cfunc_variant) = if spec.args.is_empty() {
|
||||
(quote!(noargs), quote!(PyCFunction))
|
||||
} else if spec.can_use_fastcall() {
|
||||
(
|
||||
quote!(fastcall_cfunction_with_keywords),
|
||||
quote!(PyCFunctionFastWithKeywords),
|
||||
)
|
||||
} else {
|
||||
let wrapper = impl_wrap_cfunction_with_keywords(cls, &spec, self_ty)?;
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Method({
|
||||
pyo3::class::PyMethodDef::cfunction_with_keywords(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
||||
#doc
|
||||
)
|
||||
#add_flags
|
||||
})
|
||||
(
|
||||
quote!(cfunction_with_keywords),
|
||||
quote!(PyCFunctionWithKeywords),
|
||||
)
|
||||
};
|
||||
let wrapper = if spec.args.is_empty() {
|
||||
impl_wrap_noargs(cls, spec, self_ty)
|
||||
} else if spec.can_use_fastcall() {
|
||||
impl_wrap_fastcall_cfunction_with_keywords(cls, &spec, self_ty)?
|
||||
} else {
|
||||
impl_wrap_cfunction_with_keywords(cls, &spec, self_ty)?
|
||||
};
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Method({
|
||||
pyo3::class::PyMethodDef:: #methoddef_meth (
|
||||
#python_name,
|
||||
pyo3::class::methods:: #cfunc_variant (#wrapper),
|
||||
#doc
|
||||
)
|
||||
#add_flags
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
|
||||
|
@ -707,18 +785,11 @@ pub fn impl_py_method_def_call(
|
|||
pub(crate) fn impl_py_setter_def(
|
||||
cls: &syn::Type,
|
||||
property_type: PropertyType,
|
||||
self_ty: &SelfType,
|
||||
doc: &syn::LitStr,
|
||||
deprecations: &Deprecations,
|
||||
) -> Result<TokenStream> {
|
||||
let python_name = match property_type {
|
||||
PropertyType::Descriptor(ident) => {
|
||||
let formatted_name = format!("{}\0", ident.unraw());
|
||||
quote!(#formatted_name)
|
||||
}
|
||||
PropertyType::Function(spec) => spec.null_terminated_python_name(),
|
||||
};
|
||||
let wrapper = impl_wrap_setter(cls, property_type, self_ty)?;
|
||||
let python_name = property_type.null_terminated_python_name()?;
|
||||
let deprecations = property_type.deprecations();
|
||||
let doc = property_type.doc();
|
||||
let wrapper = impl_wrap_setter(cls, &property_type)?;
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Setter({
|
||||
#deprecations
|
||||
|
@ -734,18 +805,11 @@ pub(crate) fn impl_py_setter_def(
|
|||
pub(crate) fn impl_py_getter_def(
|
||||
cls: &syn::Type,
|
||||
property_type: PropertyType,
|
||||
self_ty: &SelfType,
|
||||
doc: &syn::LitStr,
|
||||
deprecations: &Deprecations,
|
||||
) -> Result<TokenStream> {
|
||||
let python_name = match property_type {
|
||||
PropertyType::Descriptor(ident) => {
|
||||
let formatted_name = format!("{}\0", ident.unraw());
|
||||
quote!(#formatted_name)
|
||||
}
|
||||
PropertyType::Function(spec) => spec.null_terminated_python_name(),
|
||||
};
|
||||
let wrapper = impl_wrap_getter(cls, property_type, self_ty)?;
|
||||
let python_name = property_type.null_terminated_python_name()?;
|
||||
let deprecations = property_type.deprecations();
|
||||
let doc = property_type.doc();
|
||||
let wrapper = impl_wrap_getter(cls, &property_type)?;
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Getter({
|
||||
#deprecations
|
||||
|
@ -770,3 +834,53 @@ fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&FnArg>, &[FnArg])
|
|||
(None, args)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PropertyType<'a> {
|
||||
Descriptor {
|
||||
field_index: usize,
|
||||
field: &'a syn::Field,
|
||||
python_name: Option<&'a NameAttribute>,
|
||||
},
|
||||
Function {
|
||||
self_type: &'a SelfType,
|
||||
spec: &'a FnSpec<'a>,
|
||||
},
|
||||
}
|
||||
|
||||
impl PropertyType<'_> {
|
||||
fn null_terminated_python_name(&self) -> Result<syn::LitStr> {
|
||||
match self {
|
||||
PropertyType::Descriptor {
|
||||
field, python_name, ..
|
||||
} => {
|
||||
let name = match (python_name, &field.ident) {
|
||||
(Some(name), _) => name.0.to_string(),
|
||||
(None, Some(field_name)) => format!("{}\0", field_name.unraw()),
|
||||
(None, None) => {
|
||||
bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`")
|
||||
}
|
||||
};
|
||||
Ok(syn::LitStr::new(&name, field.span()))
|
||||
}
|
||||
PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name()),
|
||||
}
|
||||
}
|
||||
|
||||
fn deprecations(&self) -> Option<&Deprecations> {
|
||||
match self {
|
||||
PropertyType::Descriptor { .. } => None,
|
||||
PropertyType::Function { spec, .. } => Some(&spec.deprecations),
|
||||
}
|
||||
}
|
||||
|
||||
fn doc(&self) -> Cow<syn::LitStr> {
|
||||
match self {
|
||||
PropertyType::Descriptor { field, .. } => {
|
||||
let doc = utils::get_doc(&field.attrs, None, true)
|
||||
.unwrap_or_else(|_| syn::LitStr::new("", Span::call_site()));
|
||||
Cow::Owned(doc)
|
||||
}
|
||||
PropertyType::Function { spec, .. } => Cow::Borrowed(&spec.doc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ pub enum PyMethodDefType {
|
|||
pub enum PyMethodType {
|
||||
PyCFunction(PyCFunction),
|
||||
PyCFunctionWithKeywords(PyCFunctionWithKeywords),
|
||||
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
|
||||
PyCFunctionFastWithKeywords(PyCFunctionFastWithKeywords),
|
||||
}
|
||||
|
||||
// These newtype structs serve no purpose other than wrapping which are function pointers - because
|
||||
|
@ -36,6 +38,9 @@ pub enum PyMethodType {
|
|||
pub struct PyCFunction(pub ffi::PyCFunction);
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PyCFunctionWithKeywords(pub ffi::PyCFunctionWithKeywords);
|
||||
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PyCFunctionFastWithKeywords(pub ffi::_PyCFunctionFastWithKeywords);
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PyGetter(pub ffi::getter);
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
@ -105,6 +110,21 @@ impl PyMethodDef {
|
|||
}
|
||||
}
|
||||
|
||||
/// Define a function that can take `*args` and `**kwargs`.
|
||||
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
|
||||
pub const fn fastcall_cfunction_with_keywords(
|
||||
name: &'static str,
|
||||
cfunction: PyCFunctionFastWithKeywords,
|
||||
doc: &'static str,
|
||||
) -> Self {
|
||||
Self {
|
||||
ml_name: name,
|
||||
ml_meth: PyMethodType::PyCFunctionFastWithKeywords(cfunction),
|
||||
ml_flags: ffi::METH_FASTCALL | ffi::METH_KEYWORDS,
|
||||
ml_doc: doc,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn flags(mut self, flags: c_int) -> Self {
|
||||
self.ml_flags |= flags;
|
||||
self
|
||||
|
@ -115,6 +135,10 @@ impl PyMethodDef {
|
|||
let meth = match self.ml_meth {
|
||||
PyMethodType::PyCFunction(meth) => meth.0,
|
||||
PyMethodType::PyCFunctionWithKeywords(meth) => unsafe { std::mem::transmute(meth.0) },
|
||||
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
|
||||
PyMethodType::PyCFunctionFastWithKeywords(meth) => unsafe {
|
||||
std::mem::transmute(meth.0)
|
||||
},
|
||||
};
|
||||
|
||||
Ok(ffi::PyMethodDef {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::{FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject};
|
||||
use crate::types::PyType;
|
||||
use crate::{FromPyObject, IntoPy, PyAny, PyNativeType, PyObject, PyResult, Python, ToPyObject};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -13,7 +14,21 @@ impl ToPyObject for Path {
|
|||
|
||||
impl FromPyObject<'_> for PathBuf {
|
||||
fn extract(ob: &PyAny) -> PyResult<Self> {
|
||||
Ok(PathBuf::from(OsString::extract(ob)?))
|
||||
let os_str = match OsString::extract(ob) {
|
||||
Ok(s) => s,
|
||||
Err(err) => {
|
||||
let py = ob.py();
|
||||
let pathlib = py.import("pathlib")?;
|
||||
let pathlib_path: &PyType = pathlib.getattr("Path")?.downcast()?;
|
||||
if pathlib_path.is_instance(ob)? {
|
||||
let path_str = ob.call_method0("__str__")?;
|
||||
OsString::extract(path_str)?
|
||||
} else {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(PathBuf::from(os_str))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ impl FunctionDescription {
|
|||
format!("{}()", self.func_name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts the `args` and `kwargs` provided into `output`, according to this function
|
||||
/// definition.
|
||||
///
|
||||
|
@ -52,8 +53,9 @@ impl FunctionDescription {
|
|||
/// Unexpected, duplicate or invalid arguments will cause this function to return `TypeError`.
|
||||
pub fn extract_arguments<'p>(
|
||||
&self,
|
||||
args: &'p PyTuple,
|
||||
kwargs: Option<&'p PyDict>,
|
||||
py: Python<'p>,
|
||||
mut args: impl ExactSizeIterator<Item = &'p PyAny>,
|
||||
kwargs: Option<impl Iterator<Item = (&'p PyAny, &'p PyAny)>>,
|
||||
output: &mut [Option<&'p PyAny>],
|
||||
) -> PyResult<(Option<&'p PyTuple>, Option<&'p PyDict>)> {
|
||||
let num_positional_parameters = self.positional_parameter_names.len();
|
||||
|
@ -66,33 +68,36 @@ impl FunctionDescription {
|
|||
);
|
||||
|
||||
// Handle positional arguments
|
||||
let (args_provided, varargs) = {
|
||||
let args_provided = {
|
||||
let args_provided = args.len();
|
||||
|
||||
if self.accept_varargs {
|
||||
(
|
||||
std::cmp::min(num_positional_parameters, args_provided),
|
||||
Some(args.slice(num_positional_parameters as isize, args_provided as isize)),
|
||||
)
|
||||
std::cmp::min(num_positional_parameters, args_provided)
|
||||
} else if args_provided > num_positional_parameters {
|
||||
return Err(self.too_many_positional_arguments(args_provided));
|
||||
} else {
|
||||
(args_provided, None)
|
||||
args_provided
|
||||
}
|
||||
};
|
||||
|
||||
// Copy positional arguments into output
|
||||
for (out, arg) in output[..args_provided].iter_mut().zip(args) {
|
||||
for (out, arg) in output[..args_provided].iter_mut().zip(args.by_ref()) {
|
||||
*out = Some(arg);
|
||||
}
|
||||
|
||||
// Collect varargs into tuple
|
||||
let varargs = if self.accept_varargs {
|
||||
Some(PyTuple::new(py, args))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Handle keyword arguments
|
||||
let varkeywords = match (kwargs, self.accept_varkeywords) {
|
||||
(Some(kwargs), true) => {
|
||||
let mut varkeywords = None;
|
||||
self.extract_keyword_arguments(kwargs, output, |name, value| {
|
||||
varkeywords
|
||||
.get_or_insert_with(|| PyDict::new(kwargs.py()))
|
||||
.get_or_insert_with(|| PyDict::new(py))
|
||||
.set_item(name, value)
|
||||
})?;
|
||||
varkeywords
|
||||
|
@ -146,7 +151,7 @@ impl FunctionDescription {
|
|||
#[inline]
|
||||
fn extract_keyword_arguments<'p>(
|
||||
&self,
|
||||
kwargs: &'p PyDict,
|
||||
kwargs: impl Iterator<Item = (&'p PyAny, &'p PyAny)>,
|
||||
output: &mut [Option<&'p PyAny>],
|
||||
mut unexpected_keyword_handler: impl FnMut(&'p PyAny, &'p PyAny) -> PyResult<()>,
|
||||
) -> PyResult<()> {
|
||||
|
|
|
@ -45,7 +45,7 @@ pub type PyCFunctionWithKeywords = unsafe extern "C" fn(
|
|||
kwds: *mut PyObject,
|
||||
) -> *mut PyObject;
|
||||
|
||||
#[cfg(Py_3_7)]
|
||||
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
|
||||
pub type _PyCFunctionFastWithKeywords = unsafe extern "C" fn(
|
||||
slf: *mut PyObject,
|
||||
args: *const *mut PyObject,
|
||||
|
|
30
src/lib.rs
30
src/lib.rs
|
@ -337,35 +337,6 @@ macro_rules! wrap_pyfunction {
|
|||
};
|
||||
}
|
||||
|
||||
/// Returns the function that is called in the C-FFI.
|
||||
///
|
||||
/// Use this together with `#[pyfunction]` and [types::PyCFunction].
|
||||
/// ```
|
||||
/// use pyo3::prelude::*;
|
||||
/// use pyo3::types::PyCFunction;
|
||||
/// use pyo3::raw_pycfunction;
|
||||
///
|
||||
/// #[pyfunction]
|
||||
/// fn some_fun(arg: i32) -> PyResult<()> {
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// #[pymodule]
|
||||
/// fn module(_py: Python, module: &PyModule) -> PyResult<()> {
|
||||
/// let ffi_wrapper_fun = raw_pycfunction!(some_fun);
|
||||
/// let docs = "Some documentation string with null-termination\0";
|
||||
/// let py_cfunction =
|
||||
/// PyCFunction::new_with_keywords(ffi_wrapper_fun, "function_name", docs, module.into())?;
|
||||
/// module.add_function(py_cfunction)
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! raw_pycfunction {
|
||||
($function_name: ident) => {{
|
||||
pyo3::paste::expr! { [<__pyo3_raw_ $function_name>] }
|
||||
}};
|
||||
}
|
||||
|
||||
/// Returns a function that takes a [Python] instance and returns a Python module.
|
||||
///
|
||||
/// Use this together with `#[pymodule]` and [types::PyModule::add_wrapped].
|
||||
|
@ -538,4 +509,5 @@ pub mod doc_test {
|
|||
doctest!("guide/src/rust_cpython.md", guide_rust_cpython_md);
|
||||
doctest!("guide/src/trait_bounds.md", guide_trait_bounds_md);
|
||||
doctest!("guide/src/types.md", guide_types_md);
|
||||
doctest!("guide/src/faq.md", faq);
|
||||
}
|
||||
|
|
|
@ -14,8 +14,6 @@ pyobject_native_type_core!(PyCFunction, ffi::PyCFunction_Type, #checkfunction=ff
|
|||
|
||||
impl PyCFunction {
|
||||
/// Create a new built-in function with keywords.
|
||||
///
|
||||
/// See [raw_pycfunction] for documentation on how to get the `fun` argument.
|
||||
pub fn new_with_keywords<'a>(
|
||||
fun: ffi::PyCFunctionWithKeywords,
|
||||
name: &'static str,
|
||||
|
|
|
@ -26,6 +26,9 @@ impl PyTuple {
|
|||
unsafe {
|
||||
let ptr = ffi::PyTuple_New(len as Py_ssize_t);
|
||||
for (i, e) in elements_iter.enumerate() {
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
ffi::PyTuple_SET_ITEM(ptr, i as Py_ssize_t, e.to_object(py).into_ptr());
|
||||
#[cfg(Py_LIMITED_API)]
|
||||
ffi::PyTuple_SetItem(ptr, i as Py_ssize_t, e.to_object(py).into_ptr());
|
||||
}
|
||||
py.from_owned_ptr(ptr)
|
||||
|
@ -40,8 +43,12 @@ impl PyTuple {
|
|||
/// Gets the length of the tuple.
|
||||
pub fn len(&self) -> usize {
|
||||
unsafe {
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
let size = ffi::PyTuple_GET_SIZE(self.as_ptr());
|
||||
#[cfg(Py_LIMITED_API)]
|
||||
let size = ffi::PyTuple_Size(self.as_ptr());
|
||||
// non-negative Py_ssize_t should always fit into Rust uint
|
||||
ffi::PyTuple_Size(self.as_ptr()) as usize
|
||||
size as usize
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,8 +79,12 @@ impl PyTuple {
|
|||
pub fn get_item(&self, index: usize) -> &PyAny {
|
||||
assert!(index < self.len());
|
||||
unsafe {
|
||||
self.py()
|
||||
.from_borrowed_ptr(ffi::PyTuple_GetItem(self.as_ptr(), index as Py_ssize_t))
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
let item = ffi::PyTuple_GET_ITEM(self.as_ptr(), index as Py_ssize_t);
|
||||
#[cfg(Py_LIMITED_API)]
|
||||
let item = ffi::PyTuple_GetItem(self.as_ptr(), index as Py_ssize_t);
|
||||
|
||||
self.py().from_borrowed_ptr(item)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,6 +133,12 @@ impl<'a> Iterator for PyTupleIterator<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> ExactSizeIterator for PyTupleIterator<'a> {
|
||||
fn len(&self) -> usize {
|
||||
self.length - self.index
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a PyTuple {
|
||||
type Item = &'a PyAny;
|
||||
type IntoIter = PyTupleIterator<'a>;
|
||||
|
|
|
@ -317,7 +317,7 @@ fn test_pymethods_from_py_with() {
|
|||
}
|
||||
|
||||
#[pyclass]
|
||||
struct TupleClass(i32);
|
||||
struct TupleClass(#[pyo3(get, set, name = "value")] i32);
|
||||
|
||||
#[test]
|
||||
fn test_tuple_struct_class() {
|
||||
|
@ -326,5 +326,18 @@ fn test_tuple_struct_class() {
|
|||
assert!(typeobj.call((), None).is_err());
|
||||
|
||||
py_assert!(py, typeobj, "typeobj.__name__ == 'TupleClass'");
|
||||
|
||||
let instance = Py::new(py, TupleClass(5)).unwrap();
|
||||
py_run!(
|
||||
py,
|
||||
instance,
|
||||
r#"
|
||||
assert instance.value == 5;
|
||||
instance.value = 1234;
|
||||
assert instance.value == 1234;
|
||||
"#
|
||||
);
|
||||
|
||||
assert_eq!(instance.borrow(py).0, 1234);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -159,7 +159,7 @@ mod inheriting_native_type {
|
|||
#[pyclass(extends=PySet)]
|
||||
#[derive(Debug)]
|
||||
struct SetWithName {
|
||||
#[pyo3(get(name))]
|
||||
#[pyo3(get, name = "name")]
|
||||
_name: &'static str,
|
||||
}
|
||||
|
||||
|
@ -179,14 +179,14 @@ mod inheriting_native_type {
|
|||
py_run!(
|
||||
py,
|
||||
set_sub,
|
||||
r#"set_sub.add(10); assert list(set_sub) == [10]; assert set_sub._name == "Hello :)""#
|
||||
r#"set_sub.add(10); assert list(set_sub) == [10]; assert set_sub.name == "Hello :)""#
|
||||
);
|
||||
}
|
||||
|
||||
#[pyclass(extends=PyDict)]
|
||||
#[derive(Debug)]
|
||||
struct DictWithName {
|
||||
#[pyo3(get(name))]
|
||||
#[pyo3(get, name = "name")]
|
||||
_name: &'static str,
|
||||
}
|
||||
|
||||
|
@ -206,7 +206,7 @@ mod inheriting_native_type {
|
|||
py_run!(
|
||||
py,
|
||||
dict_sub,
|
||||
r#"dict_sub[0] = 1; assert dict_sub[0] == 1; assert dict_sub._name == "Hello :)""#
|
||||
r#"dict_sub[0] = 1; assert dict_sub[0] == 1; assert dict_sub.name == "Hello :)""#
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use pyo3::prelude::*;
|
|||
use pyo3::types::PyCFunction;
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
use pyo3::types::{PyDateTime, PyFunction};
|
||||
use pyo3::{raw_pycfunction, wrap_pyfunction};
|
||||
use pyo3::wrap_pyfunction;
|
||||
|
||||
mod common;
|
||||
|
||||
|
@ -164,31 +164,6 @@ fn test_function_with_custom_conversion_error() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_raw_function() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let raw_func = raw_pycfunction!(optional_bool);
|
||||
let fun = PyCFunction::new_with_keywords(raw_func, "fun", "", py.into()).unwrap();
|
||||
let res = fun.call((), None).unwrap().extract::<&str>().unwrap();
|
||||
assert_eq!(res, "Some(true)");
|
||||
let res = fun.call((false,), None).unwrap().extract::<&str>().unwrap();
|
||||
assert_eq!(res, "Some(false)");
|
||||
let no_module = fun.getattr("__module__").unwrap().is_none();
|
||||
assert!(no_module);
|
||||
|
||||
let module = PyModule::new(py, "cool_module").unwrap();
|
||||
module.add_function(fun).unwrap();
|
||||
let res = module
|
||||
.getattr("fun")
|
||||
.unwrap()
|
||||
.call((), None)
|
||||
.unwrap()
|
||||
.extract::<&str>()
|
||||
.unwrap();
|
||||
assert_eq!(res, "Some(true)");
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn conversion_error(str_arg: &str, int_arg: i64, tuple_arg: (&str, f64), option_arg: Option<i64>) {
|
||||
println!(
|
||||
|
|
|
@ -25,6 +25,18 @@ impl ClassWithSetter {
|
|||
}
|
||||
|
||||
#[pyclass]
|
||||
struct TupleGetterSetter(#[pyo3(get, set)] i32);
|
||||
struct TupleGetterSetterNoName(#[pyo3(get, set)] i32);
|
||||
|
||||
#[pyclass]
|
||||
struct MultipleGet(#[pyo3(get, get)] i32);
|
||||
|
||||
#[pyclass]
|
||||
struct MultipleSet(#[pyo3(set, set)] i32);
|
||||
|
||||
#[pyclass]
|
||||
struct MultipleName(#[pyo3(name = "foo", name = "bar")] i32);
|
||||
|
||||
#[pyclass]
|
||||
struct NameWithoutGetSet(#[pyo3(name = "value")] i32);
|
||||
|
||||
fn main() {}
|
||||
|
|
|
@ -16,8 +16,32 @@ error: setter function can have at most two arguments ([pyo3::Python,] and value
|
|||
24 | fn setter_with_too_many_args(&mut self, py: Python, foo: u32, bar: u32) {}
|
||||
| ^^^
|
||||
|
||||
error: `#[pyo3(get, set)]` is not supported on tuple struct fields
|
||||
--> $DIR/invalid_property_args.rs:28:44
|
||||
error: `get` and `set` with tuple struct fields require `name`
|
||||
--> $DIR/invalid_property_args.rs:28:50
|
||||
|
|
||||
28 | struct TupleGetterSetter(#[pyo3(get, set)] i32);
|
||||
| ^^^
|
||||
28 | struct TupleGetterSetterNoName(#[pyo3(get, set)] i32);
|
||||
| ^^^
|
||||
|
||||
error: `get` may only be specified once
|
||||
--> $DIR/invalid_property_args.rs:31:32
|
||||
|
|
||||
31 | struct MultipleGet(#[pyo3(get, get)] i32);
|
||||
| ^^^
|
||||
|
||||
error: `set` may only be specified once
|
||||
--> $DIR/invalid_property_args.rs:34:32
|
||||
|
|
||||
34 | struct MultipleSet(#[pyo3(set, set)] i32);
|
||||
| ^^^
|
||||
|
||||
error: `name` may only be specified once
|
||||
--> $DIR/invalid_property_args.rs:37:49
|
||||
|
|
||||
37 | struct MultipleName(#[pyo3(name = "foo", name = "bar")] i32);
|
||||
| ^^^^^
|
||||
|
||||
error: `name` is useless without `get` or `set`
|
||||
--> $DIR/invalid_property_args.rs:40:40
|
||||
|
|
||||
40 | struct NameWithoutGetSet(#[pyo3(name = "value")] i32);
|
||||
| ^^^^^^^
|
||||
|
|
Loading…
Reference in New Issue