Merge branch 'PyO3:main' into exception_docstring

This commit is contained in:
Bruno Kolenbrander 2021-12-14 17:13:24 +01:00 committed by GitHub
commit 24d761d267
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
162 changed files with 2927 additions and 2193 deletions

View file

@ -15,16 +15,16 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- run: pip install black==20.8b1
- run: pip install black==21.12b0
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: rustfmt
- name: Check python formatting (black)
run: black --check .
run: make fmt_py
- name: Check rust formatting (rustfmt)
run: cargo fmt --all -- --check
run: make fmt_rust
clippy:
runs-on: ubuntu-latest
@ -58,7 +58,7 @@ jobs:
- name: Run cargo checks
run: |
set -x
VERSIONS=("3.6" "3.7" "3.8" "3.9" "3.10")
VERSIONS=("3.7" "3.8" "3.9" "3.10")
for VERSION in ${VERSIONS[@]}; do
echo "version=$VERSION" > config.txt
echo "suppress_build_script_link_lines=true" >> config.txt
@ -76,7 +76,7 @@ jobs:
fail-fast: false # If one platform fails, allow the rest to keep testing.
matrix:
rust: [stable]
python-version: [3.6, 3.7, 3.8, 3.9, "3.10", pypy-3.6, pypy-3.7, pypy-3.8]
python-version: [3.7, 3.8, 3.9, "3.10", pypy-3.7, pypy-3.8]
platform:
[
{
@ -101,29 +101,14 @@ jobs:
},
]
exclude:
# PyPy 3.6 is EOL and not working on macos-latest (now macos-11)
- python-version: pypy-3.6
platform: { os: "macos-latest", python-architecture: "x64" }
# There is no 64-bit pypy on windows for pypy-3.6
- python-version: pypy-3.6
platform: { os: "windows-latest", python-architecture: "x64" }
# PyPy 3.7 on Windows doesn't release 32-bit builds any more
# PyPy doesn't release 32-bit Windows builds any more
- python-version: pypy-3.7
platform: { os: "windows-latest", python-architecture: "x86" }
- python-version: pypy-3.8
platform: { os: "windows-latest", python-architecture: "x86" }
include:
# PyPy3.6 still runs on macos-10.15
- rust: stable
python-version: pypy-3.6
platform:
{
os: "macos-10.15",
python-architecture: "x64",
rust-target: "x86_64-apple-darwin",
}
# Test minimal supported Rust version
- rust: 1.41.1
- rust: 1.48.0
python-version: "3.10"
platform:
{
@ -200,7 +185,7 @@ jobs:
# Run tests (except on PyPy, because no embedding API).
- if: ${{ !startsWith(matrix.python-version, 'pypy') }}
name: Test (no features)
run: cargo test --no-default-features
run: cargo test --no-default-features --lib --tests
# --no-default-features when used with `cargo build/test -p` doesn't seem to work!
- name: Test pyo3-build-config (no features)
@ -211,6 +196,10 @@ jobs:
- name: Build (all additive features)
run: cargo build --lib --tests --no-default-features --features "${{ steps.settings.outputs.all_additive_features }}"
- if: ${{ startsWith(matrix.python-version, 'pypy') }}
name: Build PyPy (abi3-py37)
run: cargo build --lib --tests --no-default-features --features "abi3-py37 ${{ steps.settings.outputs.all_additive_features }}"
# Run tests (except on PyPy, because no embedding API).
- if: ${{ !startsWith(matrix.python-version, 'pypy') }}
name: Test
@ -221,10 +210,10 @@ jobs:
name: Test (abi3)
run: cargo test --no-default-features --features "abi3 ${{ steps.settings.outputs.all_additive_features }}"
# Run tests again, for abi3-py36 (the minimal Python version)
- if: ${{ (!startsWith(matrix.python-version, 'pypy')) && (matrix.python-version != '3.6') }}
name: Test (abi3-py36)
run: cargo test --no-default-features --features "abi3-py36 ${{ steps.settings.outputs.all_additive_features }}"
# Run tests again, for abi3-py37 (the minimal Python version)
- if: ${{ (!startsWith(matrix.python-version, 'pypy')) && (matrix.python-version != '3.7') }}
name: Test (abi3-py37)
run: cargo test --no-default-features --features "abi3-py37 ${{ steps.settings.outputs.all_additive_features }}"
- name: Test proc-macro code
run: cargo test --manifest-path=pyo3-macros-backend/Cargo.toml
@ -263,6 +252,10 @@ jobs:
# TODO: this is a hack to workaround compile_error! warnings about auto-initialize on PyPy
# Once cargo's `resolver = "2"` is stable (~ MSRV Rust 1.52), remove this.
PYO3_CI: 1
# This is a hack to make CARGO_PRIMARY_PACKAGE always set even for the
# msrv job. MSRV is currently 1.48, but CARGO_PRIMARY_PACKAGE only came in
# 1.49.
CARGO_PRIMARY_PACKAGE: 1
coverage:
needs: [fmt]

View file

@ -6,11 +6,52 @@ PyO3 versions, please see the [migration guide](https://pyo3.rs/latest/migration
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## Unreleased
### Packaging
- Update MSRV to Rust 1.48. [#2004](https://github.com/PyO3/pyo3/pull/2004)
- Update `indoc` optional dependency to 1.0. [#2004](https://github.com/PyO3/pyo3/pull/2004)
- Update `paste` optional dependency to 1.0. [#2004](https://github.com/PyO3/pyo3/pull/2004)
- Drop support for Python 3.6, remove `abi3-py36` feature. [#2006](https://github.com/PyO3/pyo3/pull/2006)
- `pyo3-build-config` no longer enables the `resolve-config` feature by default. [#2008](https://github.com/PyO3/pyo3/pull/2008)
- Update `inventory` optional dependency to 0.2. [#2019](https://github.com/PyO3/pyo3/pull/2019)
### Added
- Add `Py::setattr` method. [#2009](https://github.com/PyO3/pyo3/pull/2009)
- Add `PyCapsule`, exposing the [Capsule API](https://docs.python.org/3/c-api/capsule.html#capsules). [#1980](https://github.com/PyO3/pyo3/pull/1980)
- All PyO3 proc-macros except the deprecated `#[pyproto]` now accept a supplemental attribute `#[pyo3(crate = "some::path")]` that specifies
where to find the `pyo3` crate, in case it has been renamed or is re-exported and not found at the crate root. [#2022](https://github.com/PyO3/pyo3/pull/2022)
### Changed
- `PyType::is_subclass`, `PyErr::is_instance` and `PyAny::is_instance` now operate run-time type object instead of a type known at compile-time. The old behavior is still available as `PyType::is_subclass_of`, `PyErr::is_instance_of` and `PyAny::is_instance_of`. [#1985](https://github.com/PyO3/pyo3/pull/1985)
- Rename some methods on `PyErr` (the old names are just marked deprecated for now): [#2026](https://github.com/PyO3/pyo3/pull/2026)
- `pytype` -> `get_type`
- `pvalue` -> `value` (and deprecate equivalent `instance`)
- `ptraceback` -> `traceback`
- `from_instance` -> `from_value`
- `into_instance` -> `into_value`
- Deprecate `PyType::is_instance`; it is inconsistent with other `is_instance` methods in PyO3. Instead of `typ.is_instance(obj)`, use `obj.is_instance(typ)`. [#2031](https://github.com/PyO3/pyo3/pull/2031)
- Optional parameters of `#[pymethods]` and `#[pyfunction]`s cannot be followed by required parameters, i.e. `fn opt_first(a: Option<i32>, b: i32) {}` is not allowed, while `fn opt_last(a:i32, b: Option<i32>) {}` is. [#2041](https://github.com/PyO3/pyo3/pull/2041)
### Removed
- Remove all functionality deprecated in PyO3 0.14. [#2007](https://github.com/PyO3/pyo3/pull/2007)
### Fixed
- Fix undefined symbol for `PyObject_HasAttr` on PyPy. [#2025](https://github.com/PyO3/pyo3/pull/2025)
- Fix memory leak in `PyErr::into_value`. [#2026](https://github.com/PyO3/pyo3/pull/2026)
- Fix clippy warning `needless-option-as-deref` in code generated by `#[pyfunction]` and `#[pymethods]`. [#2040](https://github.com/PyO3/pyo3/pull/2040)
## [0.15.1] - 2021-11-19
### Added
- Add implementations for `Py::as_ref()` and `Py::into_ref()` for `Py<PySequence>`, `Py<PyIterator>` and `Py<PyMapping>`. [#1682](https://github.com/PyO3/pyo3/pull/1682)
- Add `PyTraceback` type to represent and format Python tracebacks. [#1977](https://github.com/PyO3/pyo3/pull/1977)
### Changed
@ -19,6 +60,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fix creating `#[classattr]` by functions with the name of a known magic method. [#1969](https://github.com/PyO3/pyo3/pull/1969)
- Fix use of `catch_unwind` in `allow_threads` which can cause fatal crashes. [#1989](https://github.com/PyO3/pyo3/pull/1989)
- Fix build failure on PyPy when abi3 features are activated. [#1991](https://github.com/PyO3/pyo3/pull/1991)
- Fix mingw platform detection. [#1993](https://github.com/PyO3/pyo3/pull/1993)
- Fix panic in `__get__` implementation when accessing descriptor on type object. [#1997](https://github.com/PyO3/pyo3/pull/1997)
## [0.15.0] - 2021-11-03
@ -982,7 +1027,8 @@ Yanked
- Initial release
[unreleased]: https://github.com/pyo3/pyo3/compare/v0.15.0...HEAD
[unreleased]: https://github.com/pyo3/pyo3/compare/v0.15.1...HEAD
[0.15.1]: https://github.com/pyo3/pyo3/compare/v0.15.0...v0.15.1
[0.15.0]: https://github.com/pyo3/pyo3/compare/v0.14.5...v0.15.0
[0.14.5]: https://github.com/pyo3/pyo3/compare/v0.14.4...v0.14.5
[0.14.4]: https://github.com/pyo3/pyo3/compare/v0.14.3...v0.14.4

View file

@ -1,6 +1,6 @@
[package]
name = "pyo3"
version = "0.15.0"
version = "0.15.1"
description = "Bindings to Python interpreter"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
readme = "README.md"
@ -20,19 +20,17 @@ libc = "0.2.62"
parking_lot = "0.11.0"
# support crates for macros feature
pyo3-macros = { path = "pyo3-macros", version = "=0.15.0", optional = true }
# indoc must stay at 0.3.x for Rust 1.41 compatibility
indoc = { version = "0.3.6", optional = true }
paste = { version = "0.1.18", optional = true }
pyo3-macros = { path = "pyo3-macros", version = "=0.15.1", optional = true }
indoc = { version = "1.0.3", optional = true }
paste = { version = "1.0.6", optional = true }
unindent = { version = "0.1.4", optional = true }
# support crate for multiple-pymethods feature
# must stay at 0.1.x for Rust 1.41 compatibility
inventory = { version = "0.1.4", optional = true }
inventory = { version = "0.2.0", optional = true }
# crate integrations that can be added using the eponymous features
anyhow = { version = "1.0", optional = true }
eyre = { version = ">= 0.4, < 0.7" , optional = true }
eyre = { version = ">= 0.4, < 0.7", optional = true }
hashbrown = { version = ">= 0.9, < 0.12", optional = true }
indexmap = { version = ">= 1.6, < 1.8", optional = true }
num-bigint = { version = "0.4", optional = true }
@ -41,22 +39,15 @@ serde = { version = "1.0", optional = true }
[dev-dependencies]
assert_approx_eq = "1.1.0"
# O.3.5 uses the matches! macro, which isn't compatible with Rust 1.41
criterion = "=0.3.4"
# half and bitflags use if/match in const fn, which isn't compatible with Rust 1.41
half = "=1.7.1"
bitflags = "=1.2.1"
criterion = "0.3.5"
trybuild = "1.0.49"
rustversion = "1.0"
# 1.0.0 requires Rust 1.50
proptest = { version = "0.10.1", default-features = false, features = ["std"] }
serde_json = "1.0.61"
# features needed to run the PyO3 test suite
pyo3 = { path = ".", default-features = false, features = ["macros", "auto-initialize"] }
[build-dependencies]
pyo3-build-config = { path = "pyo3-build-config", version = "0.15.0", features = ["resolve-config"] }
pyo3-build-config = { path = "pyo3-build-config", version = "0.15.1", features = ["resolve-config"] }
[features]
default = ["macros"]
@ -76,7 +67,6 @@ extension-module = []
abi3 = ["pyo3-build-config/abi3"]
# With abi3, we can manually set the minimum Python version.
abi3-py36 = ["abi3-py37", "pyo3-build-config/abi3-py36"]
abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37"]
abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38"]
abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39"]

View file

@ -1,4 +1,4 @@
.PHONY: test test_py publish clippy lint fmt
.PHONY: test test_py publish clippy lint fmt fmt_py fmt_rust
ALL_ADDITIVE_FEATURES = macros multiple-pymethods num-bigint num-complex hashbrown serde indexmap eyre anyhow
@ -14,13 +14,18 @@ test: lint test_py
test_py:
for example in examples/*/; do TOX_TESTENV_PASSENV=RUSTUP_HOME tox -e py -c $$example || exit 1; done
fmt:
cargo fmt --all -- --check
fmt_py:
black . --check
fmt_rust:
cargo fmt --all -- --check
fmt: fmt_rust fmt_py
@true
clippy:
cargo clippy --features="$(ALL_ADDITIVE_FEATURES)" --tests -- -Dwarnings
cargo clippy --features="abi3 $(ALL_ADDITIVE_FEATURES)" --tests -- -Dwarnings
cargo clippy --features="$(ALL_ADDITIVE_FEATURES)" --all-targets --workspace -- -Dwarnings
cargo clippy --features="abi3 $(ALL_ADDITIVE_FEATURES)" --all-targets --workspace -- -Dwarnings
for example in examples/*/; do cargo clippy --manifest-path $$example/Cargo.toml -- -Dwarnings || exit 1; done
lint: fmt clippy

View file

@ -4,7 +4,7 @@
[![benchmark](https://github.com/PyO3/pyo3/actions/workflows/bench.yml/badge.svg)](https://pyo3.rs/dev/bench/)
[![codecov](https://codecov.io/gh/PyO3/pyo3/branch/main/graph/badge.svg)](https://codecov.io/gh/PyO3/pyo3)
[![crates.io](https://img.shields.io/crates/v/pyo3)](https://crates.io/crates/pyo3)
[![minimum rustc 1.41](https://img.shields.io/badge/rustc-1.41+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
[![minimum rustc 1.48](https://img.shields.io/badge/rustc-1.48+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
[![dev chat](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/PyO3/Lobby)
[![contributing notes](https://img.shields.io/badge/contribute-on%20github-Green)](https://github.com/PyO3/pyo3/blob/main/Contributing.md)
@ -17,22 +17,41 @@
## Usage
PyO3 supports the following software versions:
- Python 3.6 and up (CPython and PyPy)
- Rust 1.41 and up
- Python 3.7 and up (CPython and PyPy)
- Rust 1.48 and up
You can use PyO3 to write a native Python module in Rust, or to embed Python in a Rust binary. The following sections explain each of these in turn.
### Using Rust from Python
PyO3 can be used to generate a native Python module. The easiest way to try this out for the first time is to use [`maturin`](https://github.com/PyO3/maturin). `maturin` is a tool for building and publishing Rust-based Python packages with minimal configuration. The following steps set up some files for an example Python module, install `maturin`, and then show how build and import the Python module.
PyO3 can be used to generate a native Python module. The easiest way to try this out for the first time is to use [`maturin`](https://github.com/PyO3/maturin). `maturin` is a tool for building and publishing Rust-based Python packages with minimal configuration. The following steps install `maturin`, use it to generate and build a new Python package, and then launch Python to import and execute a function from the package.
First, create a new folder (let's call it `string_sum`) containing the following two files:
First, follow the commands below to create a new directory containing a new Python `virtualenv`, and install `maturin` into the virtualenv using Python's package manager, `pip`:
```bash
# (replace string_sum with the desired package name)
$ mkdir string_sum
$ cd string_sum
$ python -m venv .env
$ source .env/bin/activate
$ pip install maturin
```
Still inside this `string_sum` directory, now run `maturin init`. This will generate the new package source. When given the choice of bindings to use, select pyo3 bindings:
```bash
$ maturin init
✔ 🤷 What kind of bindings to use? · pyo3
✨ Done! New project created string_sum
```
The most important files generated by this command are `Cargo.toml` and `lib.rs`, which will look roughly like the following:
**`Cargo.toml`**
```toml
[package]
name = "string-sum"
name = "string_sum"
version = "0.1.0"
edition = "2018"
@ -45,9 +64,8 @@ name = "string_sum"
# crate-type = ["cdylib", "rlib"]
crate-type = ["cdylib"]
[dependencies.pyo3]
version = "0.15.0"
features = ["extension-module"]
[dependencies]
pyo3 = { version = "0.15.1", features = ["extension-module"] }
```
**`src/lib.rs`**
@ -67,21 +85,11 @@ fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
#[pymodule]
fn string_sum(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
}
```
With those two files in place, now `maturin` needs to be installed. This can be done using Python's package manager `pip`. First, load up a new Python `virtualenv`, and install `maturin` into it:
```bash
$ cd string_sum
$ python -m venv .env
$ source .env/bin/activate
$ pip install maturin
```
Now build and execute the module:
Finally, run `maturin develop`. This will build the package and install it into the Python virtualenv previously created and activated. The package is then ready to be used from `python`:
```bash
$ maturin develop
@ -92,7 +100,20 @@ $ python
'25'
```
As well as with `maturin`, it is possible to build using [`setuptools-rust`](https://github.com/PyO3/setuptools-rust) or [manually](https://pyo3.rs/latest/building_and_distribution.html#manual-builds). Both offer more flexibility than `maturin` but require further configuration.
To make changes to the package, just edit the Rust source code and then re-run `maturin develop` to recompile.
To run this all as a single copy-and-paste, use the bash script below (replace `string_sum` in the first command with the desired package name):
```bash
mkdir string_sum && cd "$_"
python -m venv .env
source .env/bin/activate
pip install maturin
maturin init --bindings pyo3
maturin develop
```
As well as with `maturin`, it is possible to build using [`setuptools-rust`](https://github.com/PyO3/setuptools-rust) or [manually](https://pyo3.rs/latest/building_and_distribution.html#manual-builds). Both offer more flexibility than `maturin` but require more configuration to get started.
### Using Python from Rust
@ -108,7 +129,7 @@ Start a new project with `cargo new` and add `pyo3` to the `Cargo.toml` like th
```toml
[dependencies.pyo3]
version = "0.15.0"
version = "0.15.1"
features = ["auto-initialize"]
```
@ -171,6 +192,7 @@ about this topic.
## Articles and other media
- [Calling Rust from Python using PyO3](https://saidvandeklundert.net/learn/2021-11-18-calling-rust-from-python-using-pyo3/) - Nov 18, 2021
- [davidhewitt's 2021 talk at Rust Manchester meetup](https://www.youtube.com/watch?v=-XyWG_klSAw&t=320s) - Aug 19, 2021
- [Incrementally porting a small Python project to Rust](https://blog.waleedkhan.name/port-python-to-rust/) - Apr 29, 2021
- [Vortexa - Integrating Rust into Python](https://www.vortexa.com/insight/integrating-rust-into-python) - Apr 12, 2021

View file

@ -4,19 +4,13 @@ use pyo3::prelude::*;
macro_rules! test_module {
($py:ident, $code:literal) => {
PyModule::from_code($py, indoc::indoc!($code), file!(), "test_module")
.expect("module creation failed")
PyModule::from_code($py, $code, file!(), "test_module").expect("module creation failed")
};
}
fn bench_call_0(b: &mut Bencher) {
Python::with_gil(|py| {
let module = test_module!(
py,
r#"
def foo(): pass
"#
);
let module = test_module!(py, "def foo(): pass");
let foo_module = module.getattr("foo").unwrap();
@ -32,10 +26,11 @@ fn bench_call_method_0(b: &mut Bencher) {
Python::with_gil(|py| {
let module = test_module!(
py,
r#"
class Foo:
def foo(self): pass
"#
"
class Foo:
def foo(self):
pass
"
);
let foo_module = module.getattr("Foo").unwrap().call0().unwrap();

View file

@ -1,49 +1,64 @@
use criterion::{criterion_group, criterion_main, Bencher, Criterion};
#[cfg(feature = "macros")]
use criterion::{criterion_group, criterion_main, Criterion};
use pyo3::{class::PyObjectProtocol, prelude::*, type_object::LazyStaticType};
#[cfg(feature = "macros")]
mod m {
use pyo3::{class::PyObjectProtocol, prelude::*, type_object::LazyStaticType};
/// This is a feature-rich class instance used to benchmark various parts of the pyclass lifecycle.
#[pyclass]
struct MyClass {
#[pyo3(get, set)]
elements: Vec<i32>,
}
#[pymethods]
impl MyClass {
#[new]
fn new(elements: Vec<i32>) -> Self {
Self { elements }
/// This is a feature-rich class instance used to benchmark various parts of the pyclass lifecycle.
#[pyclass]
struct MyClass {
#[pyo3(get, set)]
elements: Vec<i32>,
}
fn __call__(&mut self, new_element: i32) -> usize {
self.elements.push(new_element);
self.elements.len()
#[pymethods]
impl MyClass {
#[new]
fn new(elements: Vec<i32>) -> Self {
Self { elements }
}
fn __call__(&mut self, new_element: i32) -> usize {
self.elements.push(new_element);
self.elements.len()
}
}
#[pyproto]
impl PyObjectProtocol for MyClass {
/// A basic __str__ implementation.
fn __str__(&self) -> &'static str {
"MyClass"
}
}
pub fn first_time_init(b: &mut criterion::Bencher) {
let gil = Python::acquire_gil();
let py = gil.python();
b.iter(|| {
// This is using an undocumented internal PyO3 API to measure pyclass performance; please
// don't use this in your own code!
let ty = LazyStaticType::new();
ty.get_or_init::<MyClass>(py);
});
}
}
#[pyproto]
impl PyObjectProtocol for MyClass {
/// A basic __str__ implementation.
fn __str__(&self) -> &'static str {
"MyClass"
}
}
fn first_time_init(b: &mut Bencher) {
let gil = Python::acquire_gil();
let py = gil.python();
b.iter(|| {
// This is using an undocumented internal PyO3 API to measure pyclass performance; please
// don't use this in your own code!
let ty = LazyStaticType::new();
ty.get_or_init::<MyClass>(py);
});
}
#[cfg(feature = "macros")]
fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("first_time_init", first_time_init);
c.bench_function("first_time_init", m::first_time_init);
}
#[cfg(feature = "macros")]
criterion_group!(benches, criterion_benchmark);
#[cfg(feature = "macros")]
criterion_main!(benches);
#[cfg(not(feature = "macros"))]
fn main() {
unimplemented!(
"benchmarking `bench_pyclass` is only available with the `macros` feature enabled"
);
}

View file

@ -1,4 +1,4 @@
pytest>=3.5.0
setuptools-rust>=0.10.2
setuptools_rust~=1.0.0
pytest-benchmark~=3.2
pip>=21.3

View file

@ -1,3 +1,5 @@
#![cfg(not(Py_LIMITED_API))]
//! Objects related to PyBuffer and PyStr
use pyo3::buffer::PyBuffer;
use pyo3::prelude::*;

View file

@ -1,3 +1,5 @@
#![cfg(not(Py_LIMITED_API))]
use pyo3::prelude::*;
use pyo3::types::{
PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, PyTuple,
@ -49,8 +51,8 @@ fn time_with_fold<'p>(
minute: u8,
second: u8,
microsecond: u32,
tzinfo: Option<&PyTzInfo>,
fold: bool,
tzinfo: Option<&PyTzInfo>,
) -> PyResult<&'p PyTime> {
PyTime::new_with_fold(
py,

View file

@ -12,7 +12,9 @@ pub mod path;
pub mod pyclass_iter;
pub mod subclassing;
#[cfg(not(Py_LIMITED_API))]
use buf_and_str::*;
#[cfg(not(Py_LIMITED_API))]
use datetime::*;
use dict_iter::*;
use misc::*;
@ -24,7 +26,9 @@ use subclassing::*;
#[pymodule]
fn pyo3_pytests(py: Python, m: &PyModule) -> PyResult<()> {
#[cfg(not(Py_LIMITED_API))]
m.add_wrapped(wrap_pymodule!(buf_and_str))?;
#[cfg(not(Py_LIMITED_API))]
m.add_wrapped(wrap_pymodule!(datetime))?;
m.add_wrapped(wrap_pymodule!(dict_iter))?;
m.add_wrapped(wrap_pymodule!(misc))?;

View file

@ -139,7 +139,7 @@ def test_time_fold(t):
@pytest.mark.xfail(PYPY, reason="Feature not available on PyPy")
@pytest.mark.parametrize("fold", [False, True])
def test_time_fold(fold):
t = rdt.time_with_fold(0, 0, 0, 0, None, fold)
t = rdt.time_with_fold(0, 0, 0, 0, fold, None)
assert t.fold == fold

View file

@ -1,3 +1,3 @@
pytest>=3.5.0
setuptools_rust~=0.11.0
setuptools_rust~=1.0.0
pip>=21.3

View file

@ -1,3 +1,3 @@
pytest>=3.5.0
setuptools-rust>=0.10.2
setuptools_rust~=1.0.0
pytest-benchmark>=3.1.1

View file

@ -13,7 +13,7 @@ PyO3 uses a build script (backed by the [`pyo3-build-config`] crate) to determin
- The `python` executable (if it's a Python 3 interpreter).
- The `python3` executable.
You can override the Python interpreter by setting the `PYO3_PYTHON` environment variable, e.g. `PYO3_PYTHON=python3.6`, `PYO3_PYTHON=/usr/bin/python3.9`, or even a PyPy interpreter `PYO3_PYTHON=pypy3`.
You can override the Python interpreter by setting the `PYO3_PYTHON` environment variable, e.g. `PYO3_PYTHON=python3.7`, `PYO3_PYTHON=/usr/bin/python3.9`, or even a PyPy interpreter `PYO3_PYTHON=pypy3`.
Once the Python interpreter is located, `pyo3-build-config` executes it to query the information in the `sysconfig` module which is needed to configure the rest of the compilation.
@ -145,16 +145,16 @@ See the [corresponding](https://github.com/PyO3/maturin/pull/353) [PRs](https://
#### Minimum Python version for `abi3`
Because a single `abi3` wheel can be used with many different Python versions, PyO3 has feature flags `abi3-py36`, `abi3-py37`, `abi-py38` etc. to set the minimum required Python version for your `abi3` wheel.
For example, if you set the `abi3-py36` feature, your extension wheel can be used on all Python 3 versions from Python 3.6 and up. `maturin` and `setuptools-rust` will give the wheel a name like `my-extension-1.0-cp36-abi3-manylinux2020_x86_64.whl`.
Because a single `abi3` wheel can be used with many different Python versions, PyO3 has feature flags `abi3-py37`, `abi3-py38`, `abi3-py39` etc. to set the minimum required Python version for your `abi3` wheel.
For example, if you set the `abi3-py37` feature, your extension wheel can be used on all Python 3 versions from Python 3.7 and up. `maturin` and `setuptools-rust` will give the wheel a name like `my-extension-1.0-cp37-abi3-manylinux2020_x86_64.whl`.
As your extension module may be run with multiple different Python versions you may occasionally find you need to check the Python version at runtime to customize behavior. See [the relevant section of this guide](./building_and_distribution/multiple_python_versions.html#checking-the-python-version-at-runtime) on supporting multiple Python versions at runtime.
PyO3 is only able to link your extension module to api3 version up to and including your host Python version. E.g., if you set `abi3-py38` and try to compile the crate with a host of Python 3.6, the build will fail.
PyO3 is only able to link your extension module to api3 version up to and including your host Python version. E.g., if you set `abi3-py38` and try to compile the crate with a host of Python 3.7, the build will fail.
As an advanced feature, you can build PyO3 wheel without calling Python interpreter with the environment variable `PYO3_NO_PYTHON` set. On unix systems this works unconditionally; on Windows you must also set the `RUSTFLAGS` evironment variable to contain `-L native=/path/to/python/libs` so that the linker can find `python3.lib`.
> Note: If you set more that one of these api version feature flags the highest version always wins. For example, with both `abi3-py36` and `abi3-py38` set, PyO3 would build a wheel which supports Python 3.8 and up.
> Note: If you set more that one of these api version feature flags the lowest version always wins. For example, with both `abi3-py37` and `abi3-py38` set, PyO3 would build a wheel which supports Python 3.7 and up.
#### Missing features

View file

@ -865,7 +865,6 @@ impl pyo3::class::impl_::PyClassImpl for MyClass {
visitor(collector.py_class_descriptors());
visitor(collector.object_protocol_methods());
visitor(collector.async_protocol_methods());
visitor(collector.context_protocol_methods());
visitor(collector.descr_protocol_methods());
visitor(collector.mapping_protocol_methods());
visitor(collector.number_protocol_methods());

View file

@ -98,7 +98,7 @@ impl PyCounter {
fn __new__(wraps: Py<PyAny>) -> Self {
PyCounter { count: 0, wraps }
}
#[args(args="*", kwargs="**")]
fn __call__(
&mut self,
py: Python,

View file

@ -17,7 +17,7 @@ variety of Rust types, which you can check out in the implementor list of
[`FromPyObject`].
[`FromPyObject`] is also implemented for your own Rust types wrapped as Python
objects (see [the chapter about classes](class.md)). There, in order to both be
objects (see [the chapter about classes](../class.md)). There, in order to both be
able to operate on mutable references *and* satisfy Rust's rules of non-aliasing
mutable references, you have to extract the PyO3 reference wrappers [`PyRef`]
and [`PyRefMut`]. They work like the reference wrappers of
@ -413,7 +413,7 @@ enum RustyEnum {
# {
# let thing = b"foo".to_object(py);
# let error = thing.extract::<RustyEnum>(py).unwrap_err();
# assert!(error.is_instance::<pyo3::exceptions::PyTypeError>(py));
# assert!(error.is_instance_of::<pyo3::exceptions::PyTypeError>(py));
# }
#
# Ok(())

View file

@ -24,7 +24,7 @@ Valgrind is a tool to detect memory management bugs such as memory leaks.
You first need to install a debug build of Python, otherwise Valgrind won't produce usable results. In Ubuntu there's e.g. a `python3-dbg` package.
Activate an environment with the debug interpreter and recompile. If you're on Linux, use `ldd` with the name of your binary and check that you're linking e.g. `libpython3.6dm.so.1.0` instead of `libpython3.6m.so.1.0`.
Activate an environment with the debug interpreter and recompile. If you're on Linux, use `ldd` with the name of your binary and check that you're linking e.g. `libpython3.7d.so.1.0` instead of `libpython3.7.so.1.0`.
[Download the suppressions file for cpython](https://raw.githubusercontent.com/python/cpython/master/Misc/valgrind-python.supp).

View file

@ -536,12 +536,13 @@ fn main() -> PyResult<()> {
pyo3_asyncio::async_std::run(py, async move {
// verify that we are on a uvloop.Loop
Python::with_gil(|py| -> PyResult<()> {
assert!(uvloop
.as_ref(py)
.getattr("Loop")?
.downcast::<PyType>()
.unwrap()
.is_instance(pyo3_asyncio::async_std::get_current_loop(py)?)?);
assert!(pyo3_asyncio::async_std::get_current_loop(py)?.is_instance(
uvloop
.as_ref(py)
.getattr("Loop")?
.downcast::<PyType>()
.unwrap()
)?);
Ok(())
})?;

View file

@ -90,31 +90,29 @@ Python::with_gil(|py| {
});
```
If you already have a Python exception instance, you can simply call [`PyErr::from_instance`].
If you already have a Python exception object, you can simply call [`PyErr::from_value`].
```rust,ignore
PyErr::from_instance(py, err).restore(py);
PyErr::from_value(py, err).restore(py);
```
## Checking exception types
Python has an [`isinstance`](https://docs.python.org/3/library/functions.html#isinstance) method to check an object's type.
In PyO3 every native type has access to the [`PyAny::is_instance`] method which does the same thing.
In PyO3 every object has the [`PyAny::is_instance`] and [`PyAny::is_instance_of`] methods which do the same thing.
```rust
use pyo3::Python;
use pyo3::types::{PyBool, PyList};
Python::with_gil(|py| {
assert!(PyBool::new(py, true).is_instance::<PyBool>().unwrap());
assert!(PyBool::new(py, true).is_instance_of::<PyBool>().unwrap());
let list = PyList::new(py, &[1, 2, 3, 4]);
assert!(!list.is_instance::<PyBool>().unwrap());
assert!(list.is_instance::<PyList>().unwrap());
assert!(!list.is_instance_of::<PyBool>().unwrap());
assert!(list.is_instance_of::<PyList>().unwrap());
});
```
[`PyAny::is_instance`] calls the underlying [`PyType::is_instance`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyType.html#method.is_instance)
method to do the actual work.
To check the type of an exception, you can similarly do:
@ -123,7 +121,7 @@ To check the type of an exception, you can similarly do:
# use pyo3::prelude::*;
# Python::with_gil(|py| {
# let err = PyTypeError::new_err(());
err.is_instance::<PyTypeError>(py);
err.is_instance_of::<PyTypeError>(py);
# });
```
@ -136,7 +134,7 @@ which is an alias for the type `Result<T, PyErr>`.
A [`PyErr`] represents a Python exception. Errors within the PyO3 library are also exposed as
Python exceptions.
If your code has a custom error type, adding an implementation of `std::convert::From<MyError> for PyErr`
If your code has a custom error type, adding an implementation of `std::convert::From<MyError> for PyErr`
is usually enough. PyO3 will then automatically convert your error to a Python exception when needed.
The following code snippet defines a Rust error named `CustomIOError`. In its `From<CustomIOError> for PyErr`
@ -184,13 +182,13 @@ fn main() {
Python::with_gil(|py| {
let fun = pyo3::wrap_pyfunction!(connect, py).unwrap();
let err = fun.call1(("0.0.0.0",)).unwrap_err();
assert!(err.is_instance::<PyOSError>(py));
assert!(err.is_instance_of::<PyOSError>(py));
});
}
```
This has been implemented for most of Rust's standard library errors, so that you can use the `?`
("try") operator with them. The following code snippet will raise a `ValueError` in Python if
("try") operator with them. The following code snippet will raise a `ValueError` in Python if
`String::parse()` returns an error.
```rust
@ -201,21 +199,21 @@ fn parse_int(s: String) -> PyResult<usize> {
}
#
# use pyo3::exceptions::PyValueError;
#
#
# fn main() {
# Python::with_gil(|py| {
# assert_eq!(parse_int(String::from("1")).unwrap(), 1);
# assert_eq!(parse_int(String::from("1337")).unwrap(), 1337);
#
#
# assert!(parse_int(String::from("-1"))
# .unwrap_err()
# .is_instance::<PyValueError>(py));
# .is_instance_of::<PyValueError>(py));
# assert!(parse_int(String::from("foo"))
# .unwrap_err()
# .is_instance::<PyValueError>(py));
# .is_instance_of::<PyValueError>(py));
# assert!(parse_int(String::from("13.37"))
# .unwrap_err()
# .is_instance::<PyValueError>(py));
# .is_instance_of::<PyValueError>(py));
# })
# }
```
@ -256,6 +254,6 @@ defines exceptions for several standard library modules.
[`PyErr`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html
[`PyResult`]: {{#PYO3_DOCS_URL}}/pyo3/type.PyResult.html
[`PyErr::from_instance`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html#method.from_instance
[`Python::is_instance`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Python.html#method.is_instance
[`PyErr::from_value`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html#method.from_value
[`PyAny::is_instance`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#method.is_instance
[`PyAny::is_instance_of`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#method.is_instance_of

View file

@ -139,3 +139,23 @@ 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.
## I want to use the `pyo3` crate re-exported from from dependency but the proc-macros fail!
All PyO3 proc-macros (`#[pyclass]`, `#[pyfunction]`, `#[derive(FromPyObject)]`
and so on) expect the `pyo3` crate to be available under that name in your crate
root, which is the normal situation when `pyo3` is a direct dependency of your
crate.
However, when the dependency is renamed, or your crate only indirectly depends
on `pyo3`, you need to let the macro code know where to find the crate. This is
done with the `crate` attribute:
```rust
# use pyo3::prelude::*;
# pub extern crate pyo3;
# mod reexported { pub use ::pyo3; }
#[pyclass]
#[pyo3(crate = "reexported::pyo3")]
struct MyClass;
```

View file

@ -24,7 +24,7 @@ See the [building and distribution](building_and_distribution.md#py_limited_apia
### The `abi3-pyXY` features
(`abi3-py36`, `abi3-py37`, `abi3-py38`, `abi3-py39`, and `abi3-py310`)
(`abi3-py37`, `abi3-py38`, `abi3-py39`, and `abi3-py310`)
These features are extensions of the `abi3` feature to specify the exact minimum Python version which the multiple-version-wheel will support.
@ -74,10 +74,8 @@ The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use R
### `resolve-config`
The `resolve-config` feature of the `pyo3-build-config` crate controls whether that crate's
build script automatically resolves a Python interpreter / build configuration. Disabling
this feature enables this crate to be used in *library mode*. This may be desirable for
use cases where you want to read or write PyO3 build configuration files or resolve
metadata about a Python interpreter.
build script automatically resolves a Python interpreter / build configuration. This feature is primarily useful when building PyO3
itself. By default this feature is not enabled, meaning you can freely use `pyo3-build-config` as a standalone library to read or write PyO3 build configuration files or resolve metadata about a Python interpreter.
## Optional Dependencies
@ -127,5 +125,3 @@ struct User {
permissions: Vec<Py<Permission>>
}
```

View file

@ -3,6 +3,12 @@
This guide can help you upgrade code through breaking changes from one PyO3 version to the next.
For a detailed list of all changes, see the [CHANGELOG](changelog.md).
## from 0.15.* to 0.16
### Drop support for older technogies
PyO3 0.16 has increased minimum Rust version to 1.48 and minimum Python version to 3.7. This enables ore use of newer language features (enabling some of the other additions in 0.16) and simplifies maintenance of the project.
## from 0.14.* to 0.15
### Changes in sequence indexing
@ -164,7 +170,7 @@ let err: PyErr = TypeError::py_err("error message");
After:
```rust
```rust,ignore
# use pyo3::{PyErr, PyResult, Python, type_object::PyTypeObject};
# use pyo3::exceptions::{PyBaseException, PyTypeError};
# Python::with_gil(|py| -> PyResult<()> {

View file

@ -280,7 +280,7 @@ class House(object):
Err(e) => {
house.call_method1(
"__exit__",
(e.ptype(py), e.pvalue(py), e.ptraceback(py))
(e.get_type(py), e.value(py), e.traceback(py))
).unwrap();
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "pyo3-build-config"
version = "0.15.0"
version = "0.15.1"
description = "Build configuration for the PyO3 ecosystem"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"]
@ -14,14 +14,13 @@ edition = "2018"
once_cell = "1"
[features]
default = ["resolve-config"]
default = []
# Attempt to resolve a Python interpreter config for building in the build
# script. If this feature isn't enabled, the build script no-ops.
resolve-config = []
abi3 = []
abi3-py36 = ["abi3-py37"]
abi3-py37 = ["abi3-py38"]
abi3-py38 = ["abi3-py39"]
abi3-py39 = ["abi3-py310"]

View file

@ -63,7 +63,7 @@ pub fn abi3_config() -> Option<InterpreterConfig> {
abi3: true,
lib_name: None,
lib_dir: None,
build_flags: BuildFlags::abi3(),
build_flags: BuildFlags::default(),
pointer_width: None,
executable: None,
shared: true,

View file

@ -18,7 +18,7 @@ use crate::{
};
/// Minimum Python version PyO3 supports.
const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 6 };
const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 7 };
/// Maximum Python version that can be used as minimum required Python version with abi3.
const ABI3_MAX_MINOR: u8 = 9;
@ -131,12 +131,11 @@ impl InterpreterConfig {
pub fn emit_pyo3_cfgs(&self) {
// This should have been checked during pyo3-build-config build time.
assert!(self.version >= MINIMUM_SUPPORTED_VERSION);
for i in MINIMUM_SUPPORTED_VERSION.minor..=self.version.minor {
println!("cargo:rustc-cfg=Py_3_{}", i);
}
if self.abi3 {
println!("cargo:rustc-cfg=Py_LIMITED_API");
// pyo3-build-config was released when Python 3.6 was supported, so minimum flag to emit is
// Py_3_6 (to avoid silently breaking users who depend on this cfg).
for i in 6..=self.version.minor {
println!("cargo:rustc-cfg=Py_3_{}", i);
}
if self.implementation.is_pypy() {
@ -147,7 +146,9 @@ impl InterpreterConfig {
See https://foss.heptapod.net/pypy/pypy/-/issues/3397 for more information."
);
}
};
} else if self.abi3 {
println!("cargo:rustc-cfg=Py_LIMITED_API");
}
for flag in &self.build_flags.0 {
println!("cargo:rustc-cfg=py_sys_config=\"{}\"", flag)
@ -202,7 +203,7 @@ print_if_set("libdir", get_config_var("LIBDIR"))
print_if_set("base_prefix", base_prefix)
print("executable", sys.executable)
print("calcsize_pointer", struct.calcsize("P"))
print("mingw", get_platform() == "mingw")
print("mingw", get_platform().startswith("mingw"))
"#;
let output = run_python_script(interpreter.as_ref(), SCRIPT)?;
let map: HashMap<String, String> = parse_script_output(&output);
@ -221,7 +222,12 @@ print("mingw", get_platform() == "mingw")
let implementation = map["implementation"].parse()?;
let lib_name = if cfg!(windows) {
default_lib_name_windows(version, abi3, map["mingw"].as_str() == "True")
default_lib_name_windows(
version,
implementation,
abi3,
map["mingw"].as_str() == "True",
)
} else {
default_lib_name_unix(
version,
@ -255,7 +261,7 @@ print("mingw", get_platform() == "mingw")
lib_dir,
executable: map.get("executable").cloned(),
pointer_width: Some(calcsize_pointer * 8),
build_flags: BuildFlags::from_interpreter(interpreter)?.fixup(version, implementation),
build_flags: BuildFlags::from_interpreter(interpreter)?.fixup(version),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
})
@ -277,7 +283,7 @@ print("mingw", get_platform() == "mingw")
macro_rules! parse_value {
($variable:ident, $value:ident) => {
$variable = Some($value.parse().context(format!(
$variable = Some($value.trim().parse().context(format!(
concat!(
"failed to parse ",
stringify!($variable),
@ -344,14 +350,7 @@ print("mingw", get_platform() == "mingw")
lib_dir,
executable,
pointer_width,
build_flags: build_flags.unwrap_or_else(|| {
if abi3 {
BuildFlags::abi3()
} else {
BuildFlags::default()
}
.fixup(version, implementation)
}),
build_flags: build_flags.unwrap_or_default(),
suppress_build_script_link_lines: suppress_build_script_link_lines.unwrap_or(false),
extra_build_script_lines,
})
@ -554,7 +553,6 @@ fn cross_compiling() -> Result<Option<CrossCompileConfig>> {
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum BuildFlag {
WITH_THREAD,
Py_DEBUG,
Py_REF_DEBUG,
Py_TRACE_REFS,
@ -575,7 +573,6 @@ impl FromStr for BuildFlag {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"WITH_THREAD" => Ok(BuildFlag::WITH_THREAD),
"Py_DEBUG" => Ok(BuildFlag::Py_DEBUG),
"Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG),
"Py_TRACE_REFS" => Ok(BuildFlag::Py_TRACE_REFS),
@ -602,9 +599,7 @@ impl FromStr for BuildFlag {
pub struct BuildFlags(pub HashSet<BuildFlag>);
impl BuildFlags {
const ALL: [BuildFlag; 5] = [
// TODO: Remove WITH_THREAD once Python 3.6 support dropped (as it's always on).
BuildFlag::WITH_THREAD,
const ALL: [BuildFlag; 4] = [
BuildFlag::Py_DEBUG,
BuildFlag::Py_REF_DEBUG,
BuildFlag::Py_TRACE_REFS,
@ -633,9 +628,10 @@ impl BuildFlags {
/// the interpreter and printing variables of interest from
/// sysconfig.get_config_vars.
fn from_interpreter(interpreter: impl AsRef<Path>) -> Result<Self> {
// If we're on a Windows host, then Python won't have any useful config vars
// sysconfig is missing all the flags on windows, so we can't actually
// query the interpreter directly for its build flags.
if cfg!(windows) {
return Ok(Self::windows_hardcoded());
return Ok(Self::new());
}
let mut script = String::from("import sysconfig\n");
@ -662,21 +658,7 @@ impl BuildFlags {
Ok(Self(flags))
}
fn windows_hardcoded() -> Self {
// sysconfig is missing all the flags on windows, so we can't actually
// query the interpreter directly for its build flags.
let mut flags = HashSet::new();
flags.insert(BuildFlag::WITH_THREAD);
Self(flags)
}
pub fn abi3() -> Self {
let mut flags = HashSet::new();
flags.insert(BuildFlag::WITH_THREAD);
Self(flags)
}
fn fixup(mut self, version: PythonVersion, implementation: PythonImplementation) -> Self {
fn fixup(mut self, version: PythonVersion) -> Self {
if self.0.contains(&BuildFlag::Py_DEBUG) {
self.0.insert(BuildFlag::Py_REF_DEBUG);
if version <= PythonVersion::PY37 {
@ -685,11 +667,6 @@ impl BuildFlags {
}
}
// WITH_THREAD is always on for Python 3.7, and for PyPy.
if implementation == PythonImplementation::PyPy || version >= PythonVersion::PY37 {
self.0.insert(BuildFlag::WITH_THREAD);
}
self
}
}
@ -714,7 +691,7 @@ impl FromStr for BuildFlags {
fn from_str(value: &str) -> Result<Self, Self::Err> {
let mut flags = HashSet::new();
for flag in value.split(',') {
for flag in value.split_terminator(',') {
flags.insert(flag.parse().unwrap());
}
Ok(BuildFlags(flags))
@ -818,7 +795,7 @@ for key in KEYS:
)),
executable: None,
pointer_width,
build_flags: BuildFlags::from_config_map(&sysconfigdata).fixup(version, implementation),
build_flags: BuildFlags::from_config_map(&sysconfigdata).fixup(version),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
})
@ -908,10 +885,8 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
} else {
"python3.".into()
};
for f in fs::read_dir(path).expect("Path does not exist").into_iter() {
for f in fs::read_dir(path).expect("Path does not exist") {
sysconfig_paths.extend(match &f {
// Python 3.6 sysconfigdata without platform specifics
Ok(f) if f.file_name() == "_sysconfigdata.py" => vec![f.path()],
// Python 3.7+ sysconfigdata with platform specifics
Ok(f) if starts_with(f, "_sysconfigdata_") && ends_with(f, "py") => vec![f.path()],
Ok(f) if f.metadata().map_or(false, |metadata| metadata.is_dir()) => {
@ -966,7 +941,6 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
/// Find cross compilation information from sysconfigdata file
///
/// first find sysconfigdata file which follows the pattern [`_sysconfigdata_{abi}_{platform}_{multiarch}`][1]
/// on python 3.6 or greater. On python 3.5 it is simply `_sysconfigdata.py`.
///
/// [1]: https://github.com/python/cpython/blob/3.8/Lib/sysconfig.py#L348
fn load_cross_compile_from_sysconfigdata(
@ -989,11 +963,16 @@ fn windows_hardcoded_cross_compile(
version,
shared: true,
abi3,
lib_name: Some(default_lib_name_windows(version, abi3, false)),
lib_name: Some(default_lib_name_windows(
version,
PythonImplementation::CPython,
abi3,
false,
)),
lib_dir: cross_compile_config.lib_dir.to_str().map(String::from),
executable: None,
pointer_width: None,
build_flags: BuildFlags::windows_hardcoded(),
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
})
@ -1028,8 +1007,13 @@ fn load_cross_compile_config(
// This contains only the limited ABI symbols.
const WINDOWS_ABI3_LIB_NAME: &str = "python3";
fn default_lib_name_windows(version: PythonVersion, abi3: bool, mingw: bool) -> String {
if abi3 {
fn default_lib_name_windows(
version: PythonVersion,
implementation: PythonImplementation,
abi3: bool,
mingw: bool,
) -> String {
if abi3 && !implementation.is_pypy() {
WINDOWS_ABI3_LIB_NAME.to_owned()
} else if mingw {
// https://packages.msys2.org/base/mingw-w64-python
@ -1203,7 +1187,7 @@ mod tests {
fn test_config_file_roundtrip() {
let config = InterpreterConfig {
abi3: true,
build_flags: BuildFlags::abi3(),
build_flags: BuildFlags::default(),
pointer_width: Some(32),
executable: Some("executable".into()),
implementation: PythonImplementation::CPython,
@ -1258,9 +1242,9 @@ mod tests {
fn test_config_file_defaults() {
// Only version is required
assert_eq!(
InterpreterConfig::from_reader(Cursor::new("version=3.6")).unwrap(),
InterpreterConfig::from_reader(Cursor::new("version=3.7")).unwrap(),
InterpreterConfig {
version: PythonVersion { major: 3, minor: 6 },
version: PythonVersion { major: 3, minor: 7 },
implementation: PythonImplementation::CPython,
shared: true,
abi3: false,
@ -1302,45 +1286,26 @@ mod tests {
}
#[test]
fn build_flags_fixup_py36_debug() {
fn build_flags_fixup_py37_debug() {
let mut build_flags = BuildFlags::new();
build_flags.0.insert(BuildFlag::Py_DEBUG);
build_flags = build_flags.fixup(
PythonVersion { major: 3, minor: 6 },
PythonImplementation::CPython,
);
build_flags = build_flags.fixup(PythonVersion { major: 3, minor: 7 });
// On 3.6, Py_DEBUG implies Py_REF_DEBUG and Py_TRACE_REFS
// On 3.7, Py_DEBUG implies Py_REF_DEBUG and Py_TRACE_REFS
assert!(build_flags.0.contains(&BuildFlag::Py_REF_DEBUG));
assert!(build_flags.0.contains(&BuildFlag::Py_TRACE_REFS));
}
#[test]
fn build_flags_fixup_py37_debug() {
fn build_flags_fixup_py38_debug() {
let mut build_flags = BuildFlags::new();
build_flags.0.insert(BuildFlag::Py_DEBUG);
build_flags = build_flags.fixup(PythonVersion::PY37, PythonImplementation::CPython);
build_flags = build_flags.fixup(PythonVersion { major: 3, minor: 8 });
// On 3.7, Py_DEBUG implies Py_REF_DEBUG
// On 3.8, Py_DEBUG implies Py_REF_DEBUG
assert!(build_flags.0.contains(&BuildFlag::Py_REF_DEBUG));
// 3.7 always has WITH_THREAD
assert!(build_flags.0.contains(&BuildFlag::WITH_THREAD));
}
#[test]
fn build_flags_fixup_pypy() {
let mut build_flags = BuildFlags::new();
build_flags = build_flags.fixup(
PythonVersion { major: 3, minor: 6 },
PythonImplementation::PyPy,
);
// PyPy always has WITH_THREAD
assert!(build_flags.0.contains(&BuildFlag::WITH_THREAD));
}
#[test]
@ -1364,7 +1329,7 @@ mod tests {
fn windows_hardcoded_cross_compile() {
let cross_config = CrossCompileConfig {
lib_dir: "C:\\some\\path".into(),
version: Some(PythonVersion { major: 3, minor: 6 }),
version: Some(PythonVersion { major: 3, minor: 7 }),
os: "os".into(),
arch: "arch".into(),
};
@ -1373,14 +1338,14 @@ mod tests {
super::windows_hardcoded_cross_compile(cross_config).unwrap(),
InterpreterConfig {
implementation: PythonImplementation::CPython,
version: PythonVersion { major: 3, minor: 6 },
version: PythonVersion { major: 3, minor: 7 },
shared: true,
abi3: false,
lib_name: Some("python36".into()),
lib_name: Some("python37".into()),
lib_dir: Some("C:\\some\\path".into()),
executable: None,
pointer_width: None,
build_flags: BuildFlags::windows_hardcoded(),
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
}
@ -1389,22 +1354,52 @@ mod tests {
#[test]
fn default_lib_name_windows() {
use PythonImplementation::*;
assert_eq!(
super::default_lib_name_windows(PythonVersion { major: 3, minor: 6 }, false, false),
"python36",
super::default_lib_name_windows(
PythonVersion { major: 3, minor: 7 },
CPython,
false,
false
),
"python37",
);
assert_eq!(
super::default_lib_name_windows(PythonVersion { major: 3, minor: 6 }, true, false),
super::default_lib_name_windows(
PythonVersion { major: 3, minor: 7 },
CPython,
true,
false
),
"python3",
);
assert_eq!(
super::default_lib_name_windows(PythonVersion { major: 3, minor: 6 }, false, true),
"python3.6",
super::default_lib_name_windows(
PythonVersion { major: 3, minor: 7 },
CPython,
false,
true
),
"python3.7",
);
assert_eq!(
super::default_lib_name_windows(PythonVersion { major: 3, minor: 6 }, true, true),
super::default_lib_name_windows(
PythonVersion { major: 3, minor: 7 },
CPython,
true,
true
),
"python3",
);
assert_eq!(
super::default_lib_name_windows(
PythonVersion { major: 3, minor: 7 },
PyPy,
true,
false
),
"python37",
);
}
#[test]
@ -1412,8 +1407,8 @@ mod tests {
use PythonImplementation::*;
// Defaults to pythonX.Y for CPython
assert_eq!(
super::default_lib_name_unix(PythonVersion { major: 3, minor: 6 }, CPython, None),
"python3.6",
super::default_lib_name_unix(PythonVersion { major: 3, minor: 7 }, CPython, None),
"python3.7",
);
assert_eq!(
super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, CPython, None),
@ -1440,7 +1435,7 @@ mod tests {
fn interpreter_version_reduced_to_abi3() {
let mut config = InterpreterConfig {
abi3: true,
build_flags: BuildFlags::new(),
build_flags: BuildFlags::default(),
pointer_width: None,
executable: None,
implementation: PythonImplementation::CPython,
@ -1452,8 +1447,8 @@ mod tests {
extra_build_script_lines: vec![],
};
fixup_config_for_abi3(&mut config, Some(PythonVersion { major: 3, minor: 6 })).unwrap();
assert_eq!(config.version, PythonVersion { major: 3, minor: 6 });
fixup_config_for_abi3(&mut config, Some(PythonVersion { major: 3, minor: 7 })).unwrap();
assert_eq!(config.version, PythonVersion { major: 3, minor: 7 });
}
#[test]
@ -1467,16 +1462,16 @@ mod tests {
lib_dir: None,
lib_name: None,
shared: true,
version: PythonVersion { major: 3, minor: 6 },
version: PythonVersion { major: 3, minor: 7 },
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
};
assert!(
fixup_config_for_abi3(&mut config, Some(PythonVersion { major: 3, minor: 7 }))
fixup_config_for_abi3(&mut config, Some(PythonVersion { major: 3, minor: 8 }))
.unwrap_err()
.to_string()
.contains("cannot set a minimum Python version 3.7 higher than the interpreter version 3.6")
.contains("cannot set a minimum Python version 3.8 higher than the interpreter version 3.7")
);
}
@ -1535,11 +1530,11 @@ mod tests {
fn test_venv_interpreter() {
let base = OsStr::new("base");
assert_eq!(
venv_interpreter(&base, true),
venv_interpreter(base, true),
PathBuf::from_iter(&["base", "Scripts", "python.exe"])
);
assert_eq!(
venv_interpreter(&base, false),
venv_interpreter(base, false),
PathBuf::from_iter(&["base", "bin", "python"])
);
}
@ -1548,11 +1543,11 @@ mod tests {
fn test_conda_env_interpreter() {
let base = OsStr::new("base");
assert_eq!(
conda_env_interpreter(&base, true),
conda_env_interpreter(base, true),
PathBuf::from_iter(&["base", "python.exe"])
);
assert_eq!(
conda_env_interpreter(&base, false),
conda_env_interpreter(base, false),
PathBuf::from_iter(&["base", "bin", "python"])
);
}

View file

@ -25,7 +25,7 @@ pub use impl_::{BuildFlag, BuildFlags, InterpreterConfig, PythonImplementation,
///
/// | Flag | Description |
/// | ---- | ----------- |
/// | `#[cfg(Py_3_6)]`, `#[cfg(Py_3_7)]`, `#[cfg(Py_3_8)]`, `#[cfg(Py_3_9)]`, `#[cfg(Py_3_10)]` | These attributes mark code only for a given Python version and up. For example, `#[cfg(Py_3_6)]` marks code which can run on Python 3.6 **and newer**. |
/// | `#[cfg(Py_3_7)]`, `#[cfg(Py_3_8)]`, `#[cfg(Py_3_9)]`, `#[cfg(Py_3_10)]` | These attributes mark code only for a given Python version and up. For example, `#[cfg(Py_3_7)]` marks code which can run on Python 3.7 **and newer**. |
/// | `#[cfg(Py_LIMITED_API)]` | This marks code which is run when compiling with PyO3's `abi3` feature enabled. |
/// | `#[cfg(PyPy)]` | This marks code which is run when compiling for PyPy. |
///

View file

@ -1,6 +1,6 @@
[package]
name = "pyo3-macros-backend"
version = "0.15.0"
version = "0.15.1"
description = "Code generation for PyO3 package"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"]
@ -16,7 +16,7 @@ edition = "2018"
[dependencies]
quote = { version = "1", default-features = false }
proc-macro2 = { version = "1", default-features = false }
pyo3-build-config = { path = "../pyo3-build-config", version = "0.15.0", features = ["resolve-config"] }
pyo3-build-config = { path = "../pyo3-build-config", version = "0.15.1", features = ["resolve-config"] }
[dependencies.syn]
version = "1"

View file

@ -1,13 +1,10 @@
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
token::Comma,
Attribute, ExprPath, Ident, LitStr, Result, Token,
Attribute, ExprPath, Ident, LitStr, Path, Result, Token,
};
use crate::deprecations::{Deprecation, Deprecations};
pub mod kw {
syn::custom_keyword!(annotation);
syn::custom_keyword!(attribute);
@ -46,6 +43,19 @@ impl Parse for NameAttribute {
}
}
/// For specifying the path to the pyo3 crate.
#[derive(Clone, Debug, PartialEq)]
pub struct CrateAttribute(pub Path);
impl Parse for CrateAttribute {
fn parse(input: ParseStream) -> Result<Self> {
let _: Token![crate] = input.parse()?;
let _: Token![=] = input.parse()?;
let string_literal: LitStr = input.parse()?;
string_literal.parse().map(CrateAttribute)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TextSignatureAttribute {
pub kw: kw::text_signature,
@ -113,66 +123,3 @@ pub fn take_pyo3_options<T: Parse>(attrs: &mut Vec<syn::Attribute>) -> Result<Ve
})?;
Ok(out)
}
pub fn get_deprecated_name_attribute(
attr: &syn::Attribute,
deprecations: &mut Deprecations,
) -> syn::Result<Option<NameAttribute>> {
match attr.parse_meta() {
Ok(syn::Meta::NameValue(syn::MetaNameValue {
path,
lit: syn::Lit::Str(s),
..
})) if path.is_ident("name") => {
deprecations.push(Deprecation::NameAttribute, attr.span());
Ok(Some(NameAttribute(s.parse()?)))
}
_ => Ok(None),
}
}
pub fn get_deprecated_text_signature_attribute(
attr: &syn::Attribute,
deprecations: &mut Deprecations,
) -> syn::Result<Option<TextSignatureAttribute>> {
match attr.parse_meta() {
Ok(syn::Meta::NameValue(syn::MetaNameValue {
path,
lit: syn::Lit::Str(lit),
..
})) if path.is_ident("text_signature") => {
let text_signature = TextSignatureAttribute {
kw: syn::parse_quote!(text_signature),
eq_token: syn::parse_quote!(=),
lit,
};
deprecations.push(
crate::deprecations::Deprecation::TextSignatureAttribute,
attr.span(),
);
Ok(Some(text_signature))
}
_ => Ok(None),
}
}
pub fn take_deprecated_text_signature_attribute(
attrs: &mut Vec<syn::Attribute>,
deprecations: &mut Deprecations,
) -> syn::Result<Option<TextSignatureAttribute>> {
let mut text_signature = None;
let mut attrs_out = Vec::with_capacity(attrs.len());
for attr in attrs.drain(..) {
if let Some(value) = get_deprecated_text_signature_attribute(&attr, deprecations)? {
ensure_spanned!(
text_signature.is_none(),
attr.span() => "text_signature attribute already specified previously"
);
text_signature = Some(value);
} else {
attrs_out.push(attr);
}
}
*attrs = attrs_out;
Ok(text_signature)
}

View file

@ -107,13 +107,6 @@ impl PyMethod {
can_coexist: true,
}
}
const fn new(name: &'static str, proto: &'static str) -> Self {
PyMethod {
name,
proto,
can_coexist: false,
}
}
}
/// Represents a slot definition.
@ -156,20 +149,13 @@ pub const OBJECT: Proto = Proto {
.has_self(),
MethodProto::new("__str__", "PyObjectStrProtocol").has_self(),
MethodProto::new("__repr__", "PyObjectReprProtocol").has_self(),
MethodProto::new("__format__", "PyObjectFormatProtocol")
.args(&["Format"])
.has_self(),
MethodProto::new("__hash__", "PyObjectHashProtocol").has_self(),
MethodProto::new("__bytes__", "PyObjectBytesProtocol").has_self(),
MethodProto::new("__richcmp__", "PyObjectRichcmpProtocol")
.args(&["Other"])
.has_self(),
MethodProto::new("__bool__", "PyObjectBoolProtocol").has_self(),
],
py_methods: &[
PyMethod::new("__format__", "FormatProtocolImpl"),
PyMethod::new("__bytes__", "BytesProtocolImpl"),
],
py_methods: &[],
slot_defs: &[
SlotDef::new(&["__str__"], "Py_tp_str", "str"),
SlotDef::new(&["__repr__"], "Py_tp_repr", "repr"),
@ -194,15 +180,8 @@ pub const ASYNC: Proto = Proto {
MethodProto::new("__await__", "PyAsyncAwaitProtocol").args(&["Receiver"]),
MethodProto::new("__aiter__", "PyAsyncAiterProtocol").args(&["Receiver"]),
MethodProto::new("__anext__", "PyAsyncAnextProtocol").args(&["Receiver"]),
MethodProto::new("__aenter__", "PyAsyncAenterProtocol").has_self(),
MethodProto::new("__aexit__", "PyAsyncAexitProtocol")
.args(&["ExcType", "ExcValue", "Traceback"])
.has_self(),
],
py_methods: &[
PyMethod::new("__aenter__", "PyAsyncAenterProtocolImpl"),
PyMethod::new("__aexit__", "PyAsyncAexitProtocolImpl"),
],
py_methods: &[],
slot_defs: &[
SlotDef::new(&["__await__"], "Py_am_await", "await_"),
SlotDef::new(&["__aiter__"], "Py_am_aiter", "aiter"),
@ -228,22 +207,6 @@ pub const BUFFER: Proto = Proto {
],
};
pub const CONTEXT: Proto = Proto {
name: "Context",
module: "::pyo3::class::context",
methods: &[
MethodProto::new("__enter__", "PyContextEnterProtocol").has_self(),
MethodProto::new("__exit__", "PyContextExitProtocol")
.args(&["ExcType", "ExcValue", "Traceback"])
.has_self(),
],
py_methods: &[
PyMethod::new("__enter__", "PyContextEnterProtocolImpl"),
PyMethod::new("__exit__", "PyContextExitProtocolImpl"),
],
slot_defs: &[],
};
pub const GC: Proto = Proto {
name: "GC",
module: "::pyo3::class::gc",
@ -268,17 +231,8 @@ pub const DESCR: Proto = Proto {
methods: &[
MethodProto::new("__get__", "PyDescrGetProtocol").args(&["Receiver", "Inst", "Owner"]),
MethodProto::new("__set__", "PyDescrSetProtocol").args(&["Receiver", "Inst", "Value"]),
MethodProto::new("__delete__", "PyDescrDelProtocol")
.args(&["Inst"])
.has_self(),
MethodProto::new("__set_name__", "PyDescrSetNameProtocol")
.args(&["Inst"])
.has_self(),
],
py_methods: &[
PyMethod::new("__delete__", "PyDescrDelProtocolImpl"),
PyMethod::new("__set_name__", "PyDescrNameProtocolImpl"),
],
py_methods: &[],
slot_defs: &[
SlotDef::new(&["__get__"], "Py_tp_descr_get", "descr_get"),
SlotDef::new(&["__set__"], "Py_tp_descr_set", "descr_set"),
@ -313,12 +267,8 @@ pub const MAPPING: Proto = Proto {
MethodProto::new("__delitem__", "PyMappingDelItemProtocol")
.args(&["Key"])
.has_self(),
MethodProto::new("__reversed__", "PyMappingReversedProtocol").has_self(),
],
py_methods: &[PyMethod::new(
"__reversed__",
"PyMappingReversedProtocolImpl",
)],
py_methods: &[],
slot_defs: &[
SlotDef::new(&["__len__"], "Py_mp_length", "len"),
SlotDef::new(&["__getitem__"], "Py_mp_subscript", "getitem"),
@ -492,13 +442,9 @@ pub const NUM: Proto = Proto {
MethodProto::new("__pos__", "PyNumberPosProtocol").has_self(),
MethodProto::new("__abs__", "PyNumberAbsProtocol").has_self(),
MethodProto::new("__invert__", "PyNumberInvertProtocol").has_self(),
MethodProto::new("__complex__", "PyNumberComplexProtocol").has_self(),
MethodProto::new("__int__", "PyNumberIntProtocol").has_self(),
MethodProto::new("__float__", "PyNumberFloatProtocol").has_self(),
MethodProto::new("__index__", "PyNumberIndexProtocol").has_self(),
MethodProto::new("__round__", "PyNumberRoundProtocol")
.args(&["NDigits"])
.has_self(),
],
py_methods: &[
PyMethod::coexist("__radd__", "PyNumberRAddProtocolImpl"),
@ -515,8 +461,6 @@ pub const NUM: Proto = Proto {
PyMethod::coexist("__rand__", "PyNumberRAndProtocolImpl"),
PyMethod::coexist("__rxor__", "PyNumberRXorProtocolImpl"),
PyMethod::coexist("__ror__", "PyNumberROrProtocolImpl"),
PyMethod::new("__complex__", "PyNumberComplexProtocolImpl"),
PyMethod::new("__round__", "PyNumberRoundProtocolImpl"),
],
slot_defs: &[
SlotDef::new(&["__add__", "__radd__"], "Py_nb_add", "add_radd"),

View file

@ -2,20 +2,12 @@ use proc_macro2::{Span, TokenStream};
use quote::{quote_spanned, ToTokens};
pub enum Deprecation {
NameAttribute,
PyfnNameArgument,
PyModuleNameArgument,
TextSignatureAttribute,
CallAttribute,
}
impl Deprecation {
fn ident(&self, span: Span) -> syn::Ident {
let string = match self {
Deprecation::NameAttribute => "NAME_ATTRIBUTE",
Deprecation::PyfnNameArgument => "PYFN_NAME_ARGUMENT",
Deprecation::PyModuleNameArgument => "PYMODULE_NAME_ARGUMENT",
Deprecation::TextSignatureAttribute => "TEXT_SIGNATURE_ATTRIBUTE",
Deprecation::CallAttribute => "CALL_ATTRIBUTE",
};
syn::Ident::new(string, span)
@ -41,7 +33,7 @@ impl ToTokens for Deprecations {
let ident = deprecation.ident(*span);
quote_spanned!(
*span =>
let _ = ::pyo3::impl_::deprecations::#ident;
let _ = _pyo3::impl_::deprecations::#ident;
)
.to_tokens(tokens)
}

View file

@ -1,4 +1,7 @@
use crate::attributes::{self, get_pyo3_options, FromPyWithAttribute};
use crate::{
attributes::{self, get_pyo3_options, CrateAttribute, FromPyWithAttribute},
utils::get_pyo3_crate,
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
@ -55,15 +58,15 @@ impl<'a> Enum<'a> {
for (i, var) in self.variants.iter().enumerate() {
let struct_derive = var.build();
let ext = quote!(
let maybe_ret = || -> ::pyo3::PyResult<Self> {
let maybe_ret = || -> _pyo3::PyResult<Self> {
#struct_derive
}();
match maybe_ret {
ok @ ::std::result::Result::Ok(_) => return ok,
::std::result::Result::Err(inner) => {
let py = ::pyo3::PyNativeType::py(obj);
err_reasons.push_str(&::std::format!("{}\n", inner.instance(py).str()?));
::std::result::Result::Err(err) => {
let py = _pyo3::PyNativeType::py(obj);
err_reasons.push_str(&::std::format!("{}\n", err.value(py).str()?));
}
}
);
@ -82,7 +85,7 @@ impl<'a> Enum<'a> {
#ty_name,
#error_names,
&err_reasons);
::std::result::Result::Err(::pyo3::exceptions::PyTypeError::new_err(err_msg))
::std::result::Result::Err(_pyo3::exceptions::PyTypeError::new_err(err_msg))
)
}
}
@ -207,8 +210,8 @@ impl<'a> Container<'a> {
);
quote!(
::std::result::Result::Ok(#self_ty{#ident: obj.extract().map_err(|inner| {
let py = ::pyo3::PyNativeType::py(obj);
let new_err = ::pyo3::exceptions::PyTypeError::new_err(#error_msg);
let py = _pyo3::PyNativeType::py(obj);
let new_err = _pyo3::exceptions::PyTypeError::new_err(#error_msg);
new_err.set_cause(py, ::std::option::Option::Some(inner));
new_err
})?})
@ -221,12 +224,12 @@ impl<'a> Container<'a> {
format!("failed to extract inner field of {}", quote!(#self_ty))
};
quote!(
::std::result::Result::Ok(#self_ty(obj.extract().map_err(|inner| {
let py = ::pyo3::PyNativeType::py(obj);
::std::result::Result::Ok(#self_ty(obj.extract().map_err(|err| {
let py = _pyo3::PyNativeType::py(obj);
let err_msg = ::std::format!("{}: {}",
#error_msg,
inner.instance(py).str().unwrap());
::pyo3::exceptions::PyTypeError::new_err(err_msg)
err.value(py).str().unwrap());
_pyo3::exceptions::PyTypeError::new_err(err_msg)
})?))
)
}
@ -238,9 +241,9 @@ impl<'a> Container<'a> {
for i in 0..len {
let error_msg = format!("failed to extract field {}.{}", quote!(#self_ty), i);
fields.push(quote!(
s.get_item(#i).and_then(::pyo3::types::PyAny::extract).map_err(|inner| {
let py = ::pyo3::PyNativeType::py(obj);
let new_err = ::pyo3::exceptions::PyTypeError::new_err(#error_msg);
s.get_item(#i).and_then(_pyo3::types::PyAny::extract).map_err(|inner| {
let py = _pyo3::PyNativeType::py(obj);
let new_err = _pyo3::exceptions::PyTypeError::new_err(#error_msg);
new_err.set_cause(py, ::std::option::Option::Some(inner));
new_err
})?));
@ -255,9 +258,9 @@ impl<'a> Container<'a> {
quote!("")
};
quote!(
let s = <::pyo3::types::PyTuple as ::pyo3::conversion::PyTryFrom>::try_from(obj)?;
let s = <_pyo3::types::PyTuple as _pyo3::conversion::PyTryFrom>::try_from(obj)?;
if s.len() != #len {
return ::std::result::Result::Err(::pyo3::exceptions::PyValueError::new_err(#msg))
return ::std::result::Result::Err(_pyo3::exceptions::PyValueError::new_err(#msg))
}
::std::result::Result::Ok(#self_ty(#fields))
)
@ -279,15 +282,15 @@ impl<'a> Container<'a> {
let extractor = match &attrs.from_py_with {
None => quote!(
#get_field.extract().map_err(|inner| {
let py = ::pyo3::PyNativeType::py(obj);
let new_err = ::pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg);
let py = _pyo3::PyNativeType::py(obj);
let new_err = _pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg);
new_err.set_cause(py, ::std::option::Option::Some(inner));
new_err
})?),
Some(FromPyWithAttribute(expr_path)) => quote! (
#expr_path(#get_field).map_err(|inner| {
let py = ::pyo3::PyNativeType::py(obj);
let new_err = ::pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg);
let py = _pyo3::PyNativeType::py(obj);
let new_err = _pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg);
new_err.set_cause(py, ::std::option::Option::Some(inner));
new_err
})?
@ -300,20 +303,25 @@ impl<'a> Container<'a> {
}
}
#[derive(Default)]
struct ContainerOptions {
/// Treat the Container as a Wrapper, directly extract its fields from the input object.
transparent: bool,
/// Change the name of an enum variant in the generated error message.
annotation: Option<syn::LitStr>,
/// Change the path for the pyo3 crate
krate: Option<CrateAttribute>,
}
/// Attributes for deriving FromPyObject scoped on containers.
#[derive(Clone, Debug, PartialEq)]
#[derive(Debug)]
enum ContainerPyO3Attribute {
/// Treat the Container as a Wrapper, directly extract its fields from the input object.
Transparent(attributes::kw::transparent),
/// Change the name of an enum variant in the generated error message.
ErrorAnnotation(LitStr),
/// Change the path for the pyo3 crate
Crate(CrateAttribute),
}
impl Parse for ContainerPyO3Attribute {
@ -326,6 +334,8 @@ impl Parse for ContainerPyO3Attribute {
let _: attributes::kw::annotation = input.parse()?;
let _: Token![=] = input.parse()?;
input.parse().map(ContainerPyO3Attribute::ErrorAnnotation)
} else if lookahead.peek(Token![crate]) {
input.parse().map(ContainerPyO3Attribute::Crate)
} else {
Err(lookahead.error())
}
@ -334,10 +344,8 @@ impl Parse for ContainerPyO3Attribute {
impl ContainerOptions {
fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
let mut options = ContainerOptions {
transparent: false,
annotation: None,
};
let mut options = ContainerOptions::default();
for attr in attrs {
if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
for pyo3_attr in pyo3_attrs {
@ -356,6 +364,13 @@ impl ContainerOptions {
);
options.annotation = Some(lit_str);
}
ContainerPyO3Attribute::Crate(path) => {
ensure_spanned!(
options.krate.is_none(),
path.0.span() => "`crate` may only be provided once"
);
options.krate = Some(path);
}
}
}
}
@ -499,13 +514,18 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
.predicates
.push(parse_quote!(#gen_ident: FromPyObject<#lt_param>))
}
let options = ContainerOptions::from_attrs(&tokens.attrs)?;
let krate = get_pyo3_crate(&options.krate);
let derives = match &tokens.data {
syn::Data::Enum(en) => {
if options.transparent || options.annotation.is_some() {
bail_spanned!(tokens.span() => "`transparent` or `annotation` is not supported \
at top level for enums");
}
let en = Enum::new(en, &tokens.ident)?;
en.build()
}
syn::Data::Struct(st) => {
let options = ContainerOptions::from_attrs(&tokens.attrs)?;
if let Some(lit_str) = &options.annotation {
bail_spanned!(lit_str.span() => "`annotation` is unsupported for structs");
}
@ -520,11 +540,15 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
let ident = &tokens.ident;
Ok(quote!(
#[automatically_derived]
impl#trait_generics ::pyo3::FromPyObject<#lt_param> for #ident#generics #where_clause {
fn extract(obj: &#lt_param ::pyo3::PyAny) -> ::pyo3::PyResult<Self> {
#derives
const _: () = {
use #krate as _pyo3;
#[automatically_derived]
impl#trait_generics _pyo3::FromPyObject<#lt_param> for #ident#generics #where_clause {
fn extract(obj: &#lt_param _pyo3::PyAny) -> _pyo3::PyResult<Self> {
#derives
}
}
}
};
))
}

View file

@ -1,10 +1,7 @@
use std::borrow::Cow;
use crate::{
attributes::{
self, get_deprecated_name_attribute, get_pyo3_options, is_attribute_ident, take_attributes,
NameAttribute,
},
attributes::{self, get_pyo3_options, is_attribute_ident, take_attributes, NameAttribute},
deprecations::Deprecations,
};
use proc_macro2::{Ident, TokenStream};
@ -81,11 +78,6 @@ impl ConstAttributes {
}
}
Ok(true)
} else if let Some(name) =
get_deprecated_name_attribute(attr, &mut attributes.deprecations)?
{
attributes.set_name(name)?;
Ok(true)
} else {
Ok(false)
}

View file

@ -25,7 +25,7 @@ mod pyproto;
pub use from_pyobject::build_derive_from_pyobject;
pub use module::{process_functions_in_module, py_init, PyModuleOptions};
pub use pyclass::{build_py_class, PyClassArgs};
pub use pyclass::{build_py_class, build_py_enum, PyClassArgs};
pub use pyfunction::{build_py_function, PyFunctionOptions};
pub use pyimpl::{build_py_methods, PyClassMethodsType};
pub use pyproto::build_py_proto;

View file

@ -5,7 +5,7 @@ use crate::deprecations::Deprecation;
use crate::params::{accept_args_kwargs, impl_arg_params};
use crate::pyfunction::PyFunctionOptions;
use crate::pyfunction::{PyFunctionArgPyO3Attributes, PyFunctionSignature};
use crate::utils::{self, PythonDoc};
use crate::utils::{self, get_pyo3_crate, PythonDoc};
use crate::{deprecations::Deprecations, pyfunction::Argument};
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
@ -106,12 +106,12 @@ impl FnType {
}
FnType::FnClass => {
quote! {
let _slf = ::pyo3::types::PyType::from_type_ptr(_py, _slf as *mut ::pyo3::ffi::PyTypeObject);
let _slf = _pyo3::types::PyType::from_type_ptr(_py, _slf as *mut _pyo3::ffi::PyTypeObject);
}
}
FnType::FnModule => {
quote! {
let _slf = _py.from_borrowed_ptr::<::pyo3::types::PyModule>(_slf);
let _slf = _py.from_borrowed_ptr::<_pyo3::types::PyModule>(_slf);
}
}
}
@ -141,13 +141,13 @@ impl SelfType {
pub fn receiver(&self, cls: &syn::Type, error_mode: ExtractErrorMode) -> TokenStream {
let cell = match error_mode {
ExtractErrorMode::Raise => {
quote! { _py.from_borrowed_ptr::<::pyo3::PyAny>(_slf).downcast::<::pyo3::PyCell<#cls>>()? }
quote! { _py.from_borrowed_ptr::<_pyo3::PyAny>(_slf).downcast::<_pyo3::PyCell<#cls>>()? }
}
ExtractErrorMode::NotImplemented => {
quote! {
match _py.from_borrowed_ptr::<::pyo3::PyAny>(_slf).downcast::<::pyo3::PyCell<#cls>>() {
match _py.from_borrowed_ptr::<_pyo3::PyAny>(_slf).downcast::<_pyo3::PyCell<#cls>>() {
::std::result::Result::Ok(cell) => cell,
::std::result::Result::Err(_) => return ::pyo3::callback::convert(_py, _py.NotImplemented()),
::std::result::Result::Err(_) => return _pyo3::callback::convert(_py, _py.NotImplemented()),
}
}
}
@ -228,6 +228,7 @@ pub struct FnSpec<'a> {
pub deprecations: Deprecations,
pub convention: CallingConvention,
pub text_signature: Option<TextSignatureAttribute>,
pub krate: syn::Path,
}
pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
@ -254,12 +255,14 @@ pub fn parse_method_receiver(arg: &syn::FnArg) -> Result<SelfType> {
impl<'a> FnSpec<'a> {
/// Parser function signature and function attributes
pub fn parse(
// Signature is mutable to remove the `Python` argument.
sig: &'a mut syn::Signature,
meth_attrs: &mut Vec<syn::Attribute>,
options: PyFunctionOptions,
) -> Result<FnSpec<'a>> {
let PyFunctionOptions {
text_signature,
krate,
name,
mut deprecations,
..
@ -278,6 +281,7 @@ impl<'a> FnSpec<'a> {
let name = &sig.ident;
let ty = get_return_info(&sig.output);
let python_name = python_name.as_ref().unwrap_or(name).unraw();
let krate = get_pyo3_crate(&krate);
let doc = utils::get_doc(
meth_attrs,
@ -311,6 +315,7 @@ impl<'a> FnSpec<'a> {
doc,
deprecations,
text_signature,
krate,
})
}
@ -352,15 +357,12 @@ impl<'a> FnSpec<'a> {
parse_method_receiver(first_arg)
};
#[allow(clippy::manual_strip)] // for strip_prefix replacement supporting rust < 1.45
// strip get_ or set_
let strip_fn_name = |prefix: &'static str| {
let ident = name.unraw().to_string();
if ident.starts_with(prefix) {
Some(syn::Ident::new(&ident[prefix.len()..], ident.span()))
} else {
None
}
name.unraw()
.to_string()
.strip_prefix(prefix)
.map(|stripped| syn::Ident::new(stripped, name.span()))
};
let (fn_type, skip_first_arg, fixed_convention) = match fn_type_attr {
@ -474,17 +476,19 @@ impl<'a> FnSpec<'a> {
quote!(#func_name)
};
let rust_call =
quote! { ::pyo3::callback::convert(#py, #rust_name(#self_arg #(#arg_names),*)) };
quote! { _pyo3::callback::convert(#py, #rust_name(#self_arg #(#arg_names),*)) };
let krate = &self.krate;
Ok(match self.convention {
CallingConvention::Noargs => {
quote! {
unsafe extern "C" fn #ident (
_slf: *mut ::pyo3::ffi::PyObject,
_args: *mut ::pyo3::ffi::PyObject,
) -> *mut ::pyo3::ffi::PyObject
_slf: *mut #krate::ffi::PyObject,
_args: *mut #krate::ffi::PyObject,
) -> *mut #krate::ffi::PyObject
{
use #krate as _pyo3;
#deprecations
::pyo3::callback::handle_panic(|#py| {
_pyo3::callback::handle_panic(|#py| {
#self_conversion
#rust_call
})
@ -495,17 +499,18 @@ impl<'a> FnSpec<'a> {
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, true)?;
quote! {
unsafe extern "C" fn #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
_slf: *mut #krate::ffi::PyObject,
_args: *const *mut #krate::ffi::PyObject,
_nargs: #krate::ffi::Py_ssize_t,
_kwnames: *mut #krate::ffi::PyObject) -> *mut #krate::ffi::PyObject
{
use #krate as _pyo3;
#deprecations
::pyo3::callback::handle_panic(|#py| {
_pyo3::callback::handle_panic(|#py| {
#self_conversion
let _kwnames: ::std::option::Option<&::pyo3::types::PyTuple> = #py.from_borrowed_ptr_or_opt(_kwnames);
let _kwnames: ::std::option::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 _args = _args as *const &_pyo3::PyAny;
let _kwargs = if let ::std::option::Option::Some(kwnames) = _kwnames {
::std::slice::from_raw_parts(_args.offset(_nargs), kwnames.len())
} else {
@ -522,15 +527,16 @@ impl<'a> FnSpec<'a> {
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, false)?;
quote! {
unsafe extern "C" fn #ident (
_slf: *mut ::pyo3::ffi::PyObject,
_args: *mut ::pyo3::ffi::PyObject,
_kwargs: *mut ::pyo3::ffi::PyObject) -> *mut ::pyo3::ffi::PyObject
_slf: *mut #krate::ffi::PyObject,
_args: *mut #krate::ffi::PyObject,
_kwargs: *mut #krate::ffi::PyObject) -> *mut #krate::ffi::PyObject
{
use #krate as _pyo3;
#deprecations
::pyo3::callback::handle_panic(|#py| {
_pyo3::callback::handle_panic(|#py| {
#self_conversion
let _args = #py.from_borrowed_ptr::<::pyo3::types::PyTuple>(_args);
let _kwargs: ::std::option::Option<&::pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
let _args = #py.from_borrowed_ptr::<_pyo3::types::PyTuple>(_args);
let _kwargs: ::std::option::Option<&_pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
#arg_convert_and_rust_call
})
@ -542,20 +548,21 @@ impl<'a> FnSpec<'a> {
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, false)?;
quote! {
unsafe extern "C" fn #ident (
subtype: *mut ::pyo3::ffi::PyTypeObject,
_args: *mut ::pyo3::ffi::PyObject,
_kwargs: *mut ::pyo3::ffi::PyObject) -> *mut ::pyo3::ffi::PyObject
subtype: *mut #krate::ffi::PyTypeObject,
_args: *mut #krate::ffi::PyObject,
_kwargs: *mut #krate::ffi::PyObject) -> *mut #krate::ffi::PyObject
{
use #krate as _pyo3;
#deprecations
use ::pyo3::callback::IntoPyCallbackOutput;
::pyo3::callback::handle_panic(|#py| {
let _args = #py.from_borrowed_ptr::<::pyo3::types::PyTuple>(_args);
let _kwargs: ::std::option::Option<&::pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
use _pyo3::callback::IntoPyCallbackOutput;
_pyo3::callback::handle_panic(|#py| {
let _args = #py.from_borrowed_ptr::<_pyo3::types::PyTuple>(_args);
let _kwargs: ::std::option::Option<&_pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
let result = #arg_convert_and_rust_call;
let initializer: ::pyo3::PyClassInitializer::<#cls> = result.convert(#py)?;
let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(#py)?;
let cell = initializer.create_cell_from_subtype(#py, subtype)?;
::std::result::Result::Ok(cell as *mut ::pyo3::ffi::PyObject)
::std::result::Result::Ok(cell as *mut _pyo3::ffi::PyObject)
})
}
}
@ -570,23 +577,23 @@ impl<'a> FnSpec<'a> {
let doc = &self.doc;
match self.convention {
CallingConvention::Noargs => quote! {
::pyo3::class::methods::PyMethodDef::noargs(
_pyo3::class::methods::PyMethodDef::noargs(
#python_name,
::pyo3::class::methods::PyCFunction(#wrapper),
_pyo3::class::methods::PyCFunction(#wrapper),
#doc,
)
},
CallingConvention::Fastcall => quote! {
::pyo3::class::methods::PyMethodDef::fastcall_cfunction_with_keywords(
_pyo3::class::methods::PyMethodDef::fastcall_cfunction_with_keywords(
#python_name,
::pyo3::class::methods::PyCFunctionFastWithKeywords(#wrapper),
_pyo3::class::methods::PyCFunctionFastWithKeywords(#wrapper),
#doc,
)
},
CallingConvention::Varargs => quote! {
::pyo3::class::methods::PyMethodDef::cfunction_with_keywords(
_pyo3::class::methods::PyMethodDef::cfunction_with_keywords(
#python_name,
::pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
_pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
#doc,
)
},

View file

@ -2,14 +2,11 @@
//! Code generation for the function that initializes a python module and adds classes and function.
use crate::{
attributes::{self, take_pyo3_options},
deprecations::Deprecations,
attributes::{
self, is_attribute_ident, take_attributes, take_pyo3_options, CrateAttribute, NameAttribute,
},
pyfunction::{impl_wrap_pyfunction, PyFunctionOptions},
utils::PythonDoc,
};
use crate::{
attributes::{is_attribute_ident, take_attributes, NameAttribute},
deprecations::Deprecation,
utils::{get_pyo3_crate, PythonDoc},
};
use proc_macro2::{Span, TokenStream};
use quote::quote;
@ -21,29 +18,20 @@ use syn::{
Ident, Path, Result,
};
#[derive(Default)]
pub struct PyModuleOptions {
krate: Option<CrateAttribute>,
name: Option<syn::Ident>,
deprecations: Deprecations,
}
impl PyModuleOptions {
pub fn from_pymodule_arg_and_attrs(
deprecated_pymodule_name_arg: Option<syn::Ident>,
attrs: &mut Vec<syn::Attribute>,
) -> Result<Self> {
let mut deprecations = Deprecations::new();
if let Some(name) = &deprecated_pymodule_name_arg {
deprecations.push(Deprecation::PyModuleNameArgument, name.span());
}
let mut options: PyModuleOptions = PyModuleOptions {
name: deprecated_pymodule_name_arg,
deprecations,
};
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
let mut options: PyModuleOptions = Default::default();
for option in take_pyo3_options(attrs)? {
match option {
PyModulePyO3Option::Name(name) => options.set_name(name.0)?,
PyModulePyO3Option::Crate(path) => options.set_crate(path)?,
}
}
@ -59,13 +47,23 @@ impl PyModuleOptions {
self.name = Some(name);
Ok(())
}
fn set_crate(&mut self, path: CrateAttribute) -> Result<()> {
ensure_spanned!(
self.krate.is_none(),
path.0.span() => "`crate` may only be specified once"
);
self.krate = Some(path);
Ok(())
}
}
/// Generates the function that is called by the python interpreter to initialize the native
/// module
pub fn py_init(fnname: &Ident, options: PyModuleOptions, doc: PythonDoc) -> TokenStream {
let name = options.name.unwrap_or_else(|| fnname.unraw());
let deprecations = options.deprecations;
let krate = get_pyo3_crate(&options.krate);
let cb_name = Ident::new(&format!("PyInit_{}", name), Span::call_site());
quote! {
@ -73,15 +71,14 @@ pub fn py_init(fnname: &Ident, options: PyModuleOptions, doc: PythonDoc) -> Toke
#[allow(non_snake_case)]
/// This autogenerated function is called by the python interpreter when importing
/// the module.
pub unsafe extern "C" fn #cb_name() -> *mut ::pyo3::ffi::PyObject {
use ::pyo3::derive_utils::ModuleDef;
pub unsafe extern "C" fn #cb_name() -> *mut #krate::ffi::PyObject {
use #krate as _pyo3;
use _pyo3::derive_utils::ModuleDef;
static NAME: &str = concat!(stringify!(#name), "\0");
static DOC: &str = #doc;
static MODULE_DEF: ModuleDef = unsafe { ModuleDef::new(NAME, DOC) };
#deprecations
::pyo3::callback::handle_panic(|_py| { MODULE_DEF.make_module(_py, #fnname) })
_pyo3::callback::handle_panic(|_py| { MODULE_DEF.make_module(_py, #fnname) })
}
}
}
@ -129,23 +126,10 @@ impl Parse for PyFnArgs {
let _: Comma = input.parse()?;
let mut deprecated_name_argument = None;
if let Ok(lit_str) = input.parse::<syn::LitStr>() {
deprecated_name_argument = Some(lit_str);
if !input.is_empty() {
let _: Comma = input.parse()?;
}
}
let mut options: PyFunctionOptions = input.parse()?;
if let Some(lit_str) = deprecated_name_argument {
options.set_name(NameAttribute(lit_str.parse()?))?;
options
.deprecations
.push(Deprecation::PyfnNameArgument, lit_str.span());
}
Ok(Self { modname, options })
Ok(Self {
modname,
options: input.parse()?,
})
}
}
@ -167,13 +151,16 @@ 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_options(attrs)?;
pyfn_args
.options
.add_attributes(take_pyo3_options(attrs)?)?;
}
Ok(pyfn_args)
}
enum PyModulePyO3Option {
Crate(CrateAttribute),
Name(NameAttribute),
}
@ -182,6 +169,8 @@ impl Parse for PyModulePyO3Option {
let lookahead = input.lookahead1();
if lookahead.peek(attributes::kw::name) {
input.parse().map(PyModulePyO3Option::Name)
} else if lookahead.peek(syn::Token![crate]) {
input.parse().map(PyModulePyO3Option::Crate)
} else {
Err(lookahead.error())
}

View file

@ -82,6 +82,8 @@ pub fn impl_arg_params(
let mut required_positional_parameters = 0usize;
let mut keyword_only_parameters = Vec::new();
let mut all_positional_required = true;
for arg in spec.args.iter() {
if arg.py || is_args(&spec.attrs, arg.name) || is_kwargs(&spec.attrs, arg.name) {
continue;
@ -93,14 +95,20 @@ pub fn impl_arg_params(
if kwonly {
keyword_only_parameters.push(quote! {
::pyo3::derive_utils::KeywordOnlyParameterDescription {
_pyo3::derive_utils::KeywordOnlyParameterDescription {
name: #name,
required: #required,
}
});
} else {
if required {
ensure_spanned!(
all_positional_required,
arg.name.span() => "Required positional parameters cannot come after optional parameters"
);
required_positional_parameters += 1;
} else {
all_positional_required = false;
}
if posonly {
positional_only_parameters += 1;
@ -128,7 +136,7 @@ pub fn impl_arg_params(
let (accept_args, accept_kwargs) = accept_args_kwargs(&spec.attrs);
let cls_name = if let Some(cls) = self_ {
quote! { ::std::option::Option::Some(<#cls as ::pyo3::type_object::PyTypeInfo>::NAME) }
quote! { ::std::option::Option::Some(<#cls as _pyo3::type_object::PyTypeInfo>::NAME) }
} else {
quote! { ::std::option::Option::None }
};
@ -155,7 +163,7 @@ pub fn impl_arg_params(
// create array of arguments, and then parse
Ok(quote! {{
const DESCRIPTION: ::pyo3::derive_utils::FunctionDescription = ::pyo3::derive_utils::FunctionDescription {
const DESCRIPTION: _pyo3::derive_utils::FunctionDescription = _pyo3::derive_utils::FunctionDescription {
cls_name: #cls_name,
func_name: stringify!(#python_name),
positional_parameter_names: &[#(#positional_parameter_names),*],
@ -206,7 +214,7 @@ fn impl_arg_param(
let ty = arg.ty;
let name = arg.name;
let transform_error = quote! {
|e| ::pyo3::derive_utils::argument_extraction_error(#py, stringify!(#name), e)
|e| _pyo3::derive_utils::argument_extraction_error(#py, stringify!(#name), e)
};
if is_args(&spec.attrs, name) {
@ -275,7 +283,7 @@ fn impl_arg_param(
let (target_ty, borrow_tmp) = if arg.optional.is_some() {
// Get Option<&T> from Option<PyRef<T>>
(
quote_arg_span! { ::std::option::Option<<#tref as ::pyo3::derive_utils::ExtractExt<'_>>::Target> },
quote_arg_span! { ::std::option::Option<<#tref as _pyo3::derive_utils::ExtractExt<'_>>::Target> },
if mut_.is_some() {
quote_arg_span! { _tmp.as_deref_mut() }
} else {
@ -285,13 +293,14 @@ fn impl_arg_param(
} else {
// Get &T from PyRef<T>
(
quote_arg_span! { <#tref as ::pyo3::derive_utils::ExtractExt<'_>>::Target },
quote_arg_span! { <#tref as _pyo3::derive_utils::ExtractExt<'_>>::Target },
quote_arg_span! { &#mut_ *_tmp },
)
};
Ok(quote_arg_span! {
let #mut_ _tmp: #target_ty = #arg_value_or_default;
#[allow(clippy::needless_option_as_deref)]
let #arg_name = #borrow_tmp;
})
} else {

View file

@ -15,13 +15,11 @@ pub struct MethodProto {
}
impl MethodProto {
// TODO: workaround for no unsized casts in const fn on Rust 1.45 (stable in 1.46)
const EMPTY_ARGS: &'static [&'static str] = &[];
pub const fn new(name: &'static str, proto: &'static str) -> Self {
MethodProto {
name,
proto,
args: MethodProto::EMPTY_ARGS,
args: &[],
with_self: false,
with_result: true,
}

View file

@ -1,19 +1,26 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::attributes::{
self, take_deprecated_text_signature_attribute, take_pyo3_options, NameAttribute,
TextSignatureAttribute,
self, take_pyo3_options, CrateAttribute, NameAttribute, TextSignatureAttribute,
};
use crate::deprecations::Deprecations;
use crate::pyimpl::PyClassMethodsType;
use crate::konst::{ConstAttributes, ConstSpec};
use crate::pyimpl::{gen_default_slot_impls, gen_py_const, PyClassMethodsType};
use crate::pymethod::{impl_py_getter_def, impl_py_setter_def, PropertyType};
use crate::utils::{self, unwrap_group, PythonDoc};
use crate::utils::{self, get_pyo3_crate, unwrap_group, PythonDoc};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{parse_quote, spanned::Spanned, Expr, Result, Token};
use syn::{parse_quote, spanned::Spanned, Expr, Result, Token}; //unraw
/// If the class is derived from a Rust `struct` or `enum`.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum PyClassKind {
Struct,
Enum,
}
/// The parsed arguments of the pyclass macro
pub struct PyClassArgs {
@ -27,38 +34,43 @@ pub struct PyClassArgs {
pub has_extends: bool,
pub has_unsendable: bool,
pub module: Option<syn::LitStr>,
pub class_kind: PyClassKind,
}
impl Parse for PyClassArgs {
fn parse(input: ParseStream) -> Result<Self> {
let mut slf = PyClassArgs::default();
impl PyClassArgs {
fn parse(input: ParseStream, kind: PyClassKind) -> Result<Self> {
let mut slf = PyClassArgs::new(kind);
let vars = Punctuated::<Expr, Token![,]>::parse_terminated(input)?;
for expr in vars {
slf.add_expr(&expr)?;
}
Ok(slf)
}
}
impl Default for PyClassArgs {
fn default() -> Self {
pub fn parse_stuct_args(input: ParseStream) -> syn::Result<Self> {
Self::parse(input, PyClassKind::Struct)
}
pub fn parse_enum_args(input: ParseStream) -> syn::Result<Self> {
Self::parse(input, PyClassKind::Enum)
}
fn new(class_kind: PyClassKind) -> Self {
PyClassArgs {
freelist: None,
name: None,
module: None,
base: parse_quote! { ::pyo3::PyAny },
base: parse_quote! { _pyo3::PyAny },
has_dict: false,
has_weaklist: false,
is_gc: false,
is_basetype: false,
has_extends: false,
has_unsendable: false,
class_kind,
}
}
}
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) -> Result<()> {
@ -116,6 +128,9 @@ impl PyClassArgs {
},
"extends" => match unwrap_group(&**right) {
syn::Expr::Path(exp) => {
if self.class_kind == PyClassKind::Enum {
bail_spanned!( assign.span() => "enums cannot extend from other classes" );
}
self.base = syn::TypePath {
path: exp.path.clone(),
qself: None,
@ -150,6 +165,9 @@ impl PyClassArgs {
self.has_weaklist = true;
}
"subclass" => {
if self.class_kind == PyClassKind::Enum {
bail_spanned!(exp.span() => "enums can't be inherited by other classes");
}
self.is_basetype = true;
}
"dict" => {
@ -170,10 +188,12 @@ impl PyClassArgs {
pub struct PyClassPyO3Options {
pub text_signature: Option<TextSignatureAttribute>,
pub deprecations: Deprecations,
pub krate: Option<CrateAttribute>,
}
enum PyClassPyO3Option {
TextSignature(TextSignatureAttribute),
Crate(CrateAttribute),
}
impl Parse for PyClassPyO3Option {
@ -181,6 +201,8 @@ impl Parse for PyClassPyO3Option {
let lookahead = input.lookahead1();
if lookahead.peek(attributes::kw::text_signature) {
input.parse().map(PyClassPyO3Option::TextSignature)
} else if lookahead.peek(Token![crate]) {
input.parse().map(PyClassPyO3Option::Crate)
} else {
Err(lookahead.error())
}
@ -195,6 +217,9 @@ impl PyClassPyO3Options {
PyClassPyO3Option::TextSignature(text_signature) => {
options.set_text_signature(text_signature)?;
}
PyClassPyO3Option::Crate(path) => {
options.set_crate(path)?;
}
}
}
Ok(options)
@ -211,6 +236,15 @@ impl PyClassPyO3Options {
self.text_signature = Some(text_signature);
Ok(())
}
pub fn set_crate(&mut self, path: CrateAttribute) -> syn::Result<()> {
ensure_spanned!(
self.krate.is_none(),
path.0.span() => "`text_signature` may only be specified once"
);
self.krate = Some(path);
Ok(())
}
}
pub fn build_py_class(
@ -218,12 +252,7 @@ pub fn build_py_class(
args: &PyClassArgs,
methods_type: PyClassMethodsType,
) -> syn::Result<TokenStream> {
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 options = PyClassPyO3Options::take_pyo3_options(&mut class.attrs)?;
let doc = utils::get_doc(
&class.attrs,
options
@ -231,6 +260,7 @@ pub fn build_py_class(
.as_ref()
.map(|attr| (get_class_python_name(&class.ident, args), attr)),
);
let krate = get_pyo3_crate(&options.krate);
ensure_spanned!(
class.generics.params.is_empty(),
@ -267,6 +297,7 @@ pub fn build_py_class(
field_options,
methods_type,
options.deprecations,
krate,
)
}
@ -336,41 +367,6 @@ impl FieldPyO3Options {
}
}
/// To allow multiple #[pymethods] block, we define inventory types.
fn impl_methods_inventory(cls: &syn::Ident) -> TokenStream {
// Try to build a unique type for better error messages
let name = format!("Pyo3MethodsInventoryFor{}", cls.unraw());
let inventory_cls = syn::Ident::new(&name, Span::call_site());
quote! {
#[doc(hidden)]
pub struct #inventory_cls {
methods: ::std::vec::Vec<::pyo3::class::PyMethodDefType>,
slots: ::std::vec::Vec<::pyo3::ffi::PyType_Slot>,
}
impl ::pyo3::class::impl_::PyMethodsInventory for #inventory_cls {
fn new(
methods: ::std::vec::Vec<::pyo3::class::PyMethodDefType>,
slots: ::std::vec::Vec<::pyo3::ffi::PyType_Slot>,
) -> Self {
Self { methods, slots }
}
fn methods(&'static self) -> &'static [::pyo3::class::PyMethodDefType] {
&self.methods
}
fn slots(&'static self) -> &'static [::pyo3::ffi::PyType_Slot] {
&self.slots
}
}
impl ::pyo3::class::impl_::HasMethodsInventory for #cls {
type Methods = #inventory_cls;
}
::pyo3::inventory::collect!(#inventory_cls);
}
}
fn get_class_python_name<'a>(cls: &'a syn::Ident, attr: &'a PyClassArgs) -> &'a syn::Ident {
attr.name.as_ref().unwrap_or(cls)
}
@ -382,245 +378,162 @@ fn impl_class(
field_options: Vec<(&syn::Field, FieldPyO3Options)>,
methods_type: PyClassMethodsType,
deprecations: Deprecations,
krate: syn::Path,
) -> syn::Result<TokenStream> {
let cls_name = get_class_python_name(cls, attr).to_string();
let pytypeinfo_impl = impl_pytypeinfo(cls, attr, Some(&deprecations));
let alloc = attr.freelist.as_ref().map(|freelist| {
quote! {
impl ::pyo3::class::impl_::PyClassWithFreeList for #cls {
#[inline]
fn get_free_list(_py: ::pyo3::Python<'_>) -> &mut ::pyo3::impl_::freelist::FreeList<*mut ::pyo3::ffi::PyObject> {
static mut FREELIST: *mut ::pyo3::impl_::freelist::FreeList<*mut ::pyo3::ffi::PyObject> = 0 as *mut _;
unsafe {
if FREELIST.is_null() {
FREELIST = ::std::boxed::Box::into_raw(::std::boxed::Box::new(
::pyo3::impl_::freelist::FreeList::with_capacity(#freelist)));
}
&mut *FREELIST
}
}
}
impl ::pyo3::class::impl_::PyClassAllocImpl<#cls> for ::pyo3::class::impl_::PyClassImplCollector<#cls> {
#[inline]
fn alloc_impl(self) -> ::std::option::Option<::pyo3::ffi::allocfunc> {
::std::option::Option::Some(::pyo3::class::impl_::alloc_with_freelist::<#cls>)
}
}
impl ::pyo3::class::impl_::PyClassFreeImpl<#cls> for ::pyo3::class::impl_::PyClassImplCollector<#cls> {
#[inline]
fn free_impl(self) -> ::std::option::Option<::pyo3::ffi::freefunc> {
::std::option::Option::Some(::pyo3::class::impl_::free_with_freelist::<#cls>)
}
}
}
});
let py_class_impl = PyClassImplsBuilder::new(cls, attr, methods_type)
.doc(doc)
.impl_all();
let descriptors = impl_descriptors(cls, field_options)?;
// insert space for weak ref
let weakref = if attr.has_weaklist {
quote! { ::pyo3::pyclass_slots::PyClassWeakRefSlot }
} else if attr.has_extends {
quote! { <Self::BaseType as ::pyo3::class::impl_::PyClassBaseType>::WeakRef }
} else {
quote! { ::pyo3::pyclass_slots::PyClassDummySlot }
};
let dict = if attr.has_dict {
quote! { ::pyo3::pyclass_slots::PyClassDictSlot }
} else if attr.has_extends {
quote! { <Self::BaseType as ::pyo3::class::impl_::PyClassBaseType>::Dict }
} else {
quote! { ::pyo3::pyclass_slots::PyClassDummySlot }
};
let module = if let Some(m) = &attr.module {
quote! { ::std::option::Option::Some(#m) }
} else {
quote! { ::std::option::Option::None }
};
// Enforce at compile time that PyGCProtocol is implemented
let gc_impl = if attr.is_gc {
let closure_name = format!("__assertion_closure_{}", cls);
let closure_token = syn::Ident::new(&closure_name, Span::call_site());
quote! {
fn #closure_token() {
use ::pyo3::class;
fn _assert_implements_protocol<'p, T: ::pyo3::class::PyGCProtocol<'p>>() {}
_assert_implements_protocol::<#cls>();
}
}
} else {
quote! {}
};
let (impl_inventory, for_each_py_method) = match methods_type {
PyClassMethodsType::Specialization => (None, quote! { visitor(collector.py_methods()); }),
PyClassMethodsType::Inventory => (
Some(impl_methods_inventory(cls)),
quote! {
for inventory in ::pyo3::inventory::iter::<<Self as ::pyo3::class::impl_::HasMethodsInventory>::Methods>() {
visitor(::pyo3::class::impl_::PyMethodsInventory::methods(inventory));
}
},
),
};
let methods_protos = match methods_type {
PyClassMethodsType::Specialization => {
quote! { visitor(collector.methods_protocol_slots()); }
}
PyClassMethodsType::Inventory => {
quote! {
for inventory in ::pyo3::inventory::iter::<<Self as ::pyo3::class::impl_::HasMethodsInventory>::Methods>() {
visitor(::pyo3::class::impl_::PyMethodsInventory::slots(inventory));
}
}
}
};
let base = &attr.base;
let base_nativetype = if attr.has_extends {
quote! { <Self::BaseType as ::pyo3::class::impl_::PyClassBaseType>::BaseNativeType }
} else {
quote! { ::pyo3::PyAny }
};
// If #cls is not extended type, we allow Self->PyObject conversion
let into_pyobject = if !attr.has_extends {
quote! {
impl ::pyo3::IntoPy<::pyo3::PyObject> for #cls {
fn into_py(self, py: ::pyo3::Python) -> ::pyo3::PyObject {
::pyo3::IntoPy::into_py(::pyo3::Py::new(py, self).unwrap(), py)
}
}
}
} else {
quote! {}
};
let thread_checker = if attr.has_unsendable {
quote! { ::pyo3::class::impl_::ThreadCheckerImpl<#cls> }
} else if attr.has_extends {
quote! {
::pyo3::class::impl_::ThreadCheckerInherited<#cls, <#cls as ::pyo3::class::impl_::PyClassImpl>::BaseType>
}
} else {
quote! { ::pyo3::class::impl_::ThreadCheckerStub<#cls> }
};
let is_gc = attr.is_gc;
let is_basetype = attr.is_basetype;
let is_subclass = attr.has_extends;
Ok(quote! {
unsafe impl ::pyo3::type_object::PyTypeInfo for #cls {
type AsRefTarget = ::pyo3::PyCell<Self>;
const _: () = {
use #krate as _pyo3;
const NAME: &'static str = #cls_name;
const MODULE: ::std::option::Option<&'static str> = #module;
#pytypeinfo_impl
#[inline]
fn type_object_raw(py: ::pyo3::Python<'_>) -> *mut ::pyo3::ffi::PyTypeObject {
#deprecations
#py_class_impl
use ::pyo3::type_object::LazyStaticType;
static TYPE_OBJECT: LazyStaticType = LazyStaticType::new();
TYPE_OBJECT.get_or_init::<Self>(py)
}
}
impl ::pyo3::PyClass for #cls {
type Dict = #dict;
type WeakRef = #weakref;
type BaseNativeType = #base_nativetype;
}
impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a #cls
{
type Target = ::pyo3::PyRef<'a, #cls>;
}
impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a mut #cls
{
type Target = ::pyo3::PyRefMut<'a, #cls>;
}
#into_pyobject
#impl_inventory
impl ::pyo3::class::impl_::PyClassImpl for #cls {
const DOC: &'static str = #doc;
const IS_GC: bool = #is_gc;
const IS_BASETYPE: bool = #is_basetype;
const IS_SUBCLASS: bool = #is_subclass;
type Layout = ::pyo3::PyCell<Self>;
type BaseType = #base;
type ThreadChecker = #thread_checker;
fn for_each_method_def(visitor: &mut dyn ::std::ops::FnMut(&[::pyo3::class::PyMethodDefType])) {
use ::pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
#for_each_py_method;
visitor(collector.py_class_descriptors());
visitor(collector.object_protocol_methods());
visitor(collector.async_protocol_methods());
visitor(collector.context_protocol_methods());
visitor(collector.descr_protocol_methods());
visitor(collector.mapping_protocol_methods());
visitor(collector.number_protocol_methods());
}
fn get_new() -> ::std::option::Option<::pyo3::ffi::newfunc> {
use ::pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
collector.new_impl()
}
fn get_alloc() -> ::std::option::Option<::pyo3::ffi::allocfunc> {
use ::pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
collector.alloc_impl()
}
fn get_free() -> ::std::option::Option<::pyo3::ffi::freefunc> {
use ::pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
collector.free_impl()
}
fn for_each_proto_slot(visitor: &mut dyn ::std::ops::FnMut(&[::pyo3::ffi::PyType_Slot])) {
// Implementation which uses dtolnay specialization to load all slots.
use ::pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
visitor(collector.object_protocol_slots());
visitor(collector.number_protocol_slots());
visitor(collector.iter_protocol_slots());
visitor(collector.gc_protocol_slots());
visitor(collector.descr_protocol_slots());
visitor(collector.mapping_protocol_slots());
visitor(collector.sequence_protocol_slots());
visitor(collector.async_protocol_slots());
visitor(collector.buffer_protocol_slots());
#methods_protos
}
fn get_buffer() -> ::std::option::Option<&'static ::pyo3::class::impl_::PyBufferProcs> {
use ::pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
collector.buffer_procs()
}
}
#alloc
#descriptors
#gc_impl
#descriptors
};
})
}
struct PyClassEnumVariant<'a> {
ident: &'a syn::Ident,
/* currently have no more options */
}
pub fn build_py_enum(
enum_: &mut syn::ItemEnum,
args: &PyClassArgs,
method_type: PyClassMethodsType,
) -> syn::Result<TokenStream> {
let options = PyClassPyO3Options::take_pyo3_options(&mut enum_.attrs)?;
if enum_.variants.is_empty() {
bail_spanned!(enum_.brace_token.span => "Empty enums can't be #[pyclass].");
}
let variants: Vec<PyClassEnumVariant> = enum_
.variants
.iter()
.map(extract_variant_data)
.collect::<syn::Result<_>>()?;
impl_enum(enum_, args, variants, method_type, options)
}
fn impl_enum(
enum_: &syn::ItemEnum,
args: &PyClassArgs,
variants: Vec<PyClassEnumVariant>,
methods_type: PyClassMethodsType,
options: PyClassPyO3Options,
) -> syn::Result<TokenStream> {
let enum_name = &enum_.ident;
let doc = utils::get_doc(
&enum_.attrs,
options
.text_signature
.as_ref()
.map(|attr| (get_class_python_name(&enum_.ident, args), attr)),
);
let krate = get_pyo3_crate(&options.krate);
impl_enum_class(enum_name, args, variants, doc, methods_type, krate)
}
fn impl_enum_class(
cls: &syn::Ident,
args: &PyClassArgs,
variants: Vec<PyClassEnumVariant>,
doc: PythonDoc,
methods_type: PyClassMethodsType,
krate: syn::Path,
) -> syn::Result<TokenStream> {
let pytypeinfo = impl_pytypeinfo(cls, args, None);
let pyclass_impls = PyClassImplsBuilder::new(cls, args, methods_type)
.doc(doc)
.impl_all();
let descriptors = unit_variants_as_descriptors(cls, variants.iter().map(|v| v.ident));
let default_repr_impl = {
let variants_repr = variants.iter().map(|variant| {
let variant_name = variant.ident;
// Assuming all variants are unit variants because they are the only type we support.
let repr = format!("{}.{}", cls, variant_name);
quote! { #cls::#variant_name => #repr, }
});
quote! {
#[doc(hidden)]
#[allow(non_snake_case)]
#[pyo3(name = "__repr__")]
fn __pyo3__repr__(&self) -> &'static str {
match self {
#(#variants_repr)*
_ => unreachable!("Unsupported variant type."),
}
}
}
};
let default_impls = gen_default_slot_impls(cls, vec![default_repr_impl]);
Ok(quote! {
const _: () = {
use #krate as _pyo3;
#pytypeinfo
#pyclass_impls
#default_impls
#descriptors
};
})
}
fn unit_variants_as_descriptors<'a>(
cls: &'a syn::Ident,
variant_names: impl IntoIterator<Item = &'a syn::Ident>,
) -> TokenStream {
let cls_type = syn::parse_quote!(#cls);
let variant_to_attribute = |ident: &syn::Ident| ConstSpec {
rust_ident: ident.clone(),
attributes: ConstAttributes {
is_class_attr: true,
name: Some(NameAttribute(ident.clone())),
deprecations: Default::default(),
},
};
let py_methods = variant_names
.into_iter()
.map(|var| gen_py_const(&cls_type, &variant_to_attribute(var)));
quote! {
impl _pyo3::class::impl_::PyClassDescriptors<#cls>
for _pyo3::class::impl_::PyClassImplCollector<#cls>
{
fn py_class_descriptors(self) -> &'static [_pyo3::class::methods::PyMethodDefType] {
static METHODS: &[_pyo3::class::methods::PyMethodDefType] = &[#(#py_methods),*];
METHODS
}
}
}
}
fn extract_variant_data(variant: &syn::Variant) -> syn::Result<PyClassEnumVariant> {
use syn::Fields;
let ident = match variant.fields {
Fields::Unit => &variant.ident,
_ => bail_spanned!(variant.span() => "Currently only support unit variants."),
};
if let Some(discriminant) = variant.discriminant.as_ref() {
bail_spanned!(discriminant.0.span() => "Currently does not support discriminats.")
};
Ok(PyClassEnumVariant { ident })
}
fn impl_descriptors(
cls: &syn::Ident,
field_options: Vec<(&syn::Field, FieldPyO3Options)>,
@ -661,13 +574,358 @@ fn impl_descriptors(
.collect::<syn::Result<_>>()?;
Ok(quote! {
impl ::pyo3::class::impl_::PyClassDescriptors<#cls>
for ::pyo3::class::impl_::PyClassImplCollector<#cls>
impl _pyo3::class::impl_::PyClassDescriptors<#cls>
for _pyo3::class::impl_::PyClassImplCollector<#cls>
{
fn py_class_descriptors(self) -> &'static [::pyo3::class::methods::PyMethodDefType] {
static METHODS: &[::pyo3::class::methods::PyMethodDefType] = &[#(#py_methods),*];
fn py_class_descriptors(self) -> &'static [_pyo3::class::methods::PyMethodDefType] {
static METHODS: &[_pyo3::class::methods::PyMethodDefType] = &[#(#py_methods),*];
METHODS
}
}
})
}
fn impl_pytypeinfo(
cls: &syn::Ident,
attr: &PyClassArgs,
deprecations: Option<&Deprecations>,
) -> TokenStream {
let cls_name = get_class_python_name(cls, attr).to_string();
let module = if let Some(m) = &attr.module {
quote! { ::core::option::Option::Some(#m) }
} else {
quote! { ::core::option::Option::None }
};
quote! {
unsafe impl _pyo3::type_object::PyTypeInfo for #cls {
type AsRefTarget = _pyo3::PyCell<Self>;
const NAME: &'static str = #cls_name;
const MODULE: ::std::option::Option<&'static str> = #module;
#[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)
}
}
}
}
/// Implements most traits used by `#[pyclass]`.
///
/// Specifically, it implements traits that only depend on class name,
/// and attributes of `#[pyclass]`, and docstrings.
/// Therefore it doesn't implement traits that depends on struct fields and enum variants.
struct PyClassImplsBuilder<'a> {
cls: &'a syn::Ident,
attr: &'a PyClassArgs,
methods_type: PyClassMethodsType,
doc: Option<PythonDoc>,
}
impl<'a> PyClassImplsBuilder<'a> {
fn new(cls: &'a syn::Ident, attr: &'a PyClassArgs, methods_type: PyClassMethodsType) -> Self {
Self {
cls,
attr,
methods_type,
doc: None,
}
}
fn doc(self, doc: PythonDoc) -> Self {
Self {
doc: Some(doc),
..self
}
}
fn impl_all(&self) -> TokenStream {
vec![
self.impl_pyclass(),
self.impl_extractext(),
self.impl_into_py(),
self.impl_pyclassimpl(),
self.impl_freelist(),
self.impl_gc(),
]
.into_iter()
.collect()
}
fn impl_pyclass(&self) -> TokenStream {
let cls = self.cls;
let attr = self.attr;
let dict = if attr.has_dict {
quote! { _pyo3::pyclass_slots::PyClassDictSlot }
} else if attr.has_extends {
quote! { <Self::BaseType as _pyo3::class::impl_::PyClassBaseType>::Dict }
} else {
quote! { _pyo3::pyclass_slots::PyClassDummySlot }
};
// insert space for weak ref
let weakref = if attr.has_weaklist {
quote! { _pyo3::pyclass_slots::PyClassWeakRefSlot }
} else if attr.has_extends {
quote! { <Self::BaseType as _pyo3::class::impl_::PyClassBaseType>::WeakRef }
} else {
quote! { _pyo3::pyclass_slots::PyClassDummySlot }
};
let base_nativetype = if attr.has_extends {
quote! { <Self::BaseType as _pyo3::class::impl_::PyClassBaseType>::BaseNativeType }
} else {
quote! { _pyo3::PyAny }
};
quote! {
impl _pyo3::PyClass for #cls {
type Dict = #dict;
type WeakRef = #weakref;
type BaseNativeType = #base_nativetype;
}
}
}
fn impl_extractext(&self) -> TokenStream {
let cls = self.cls;
quote! {
impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a #cls
{
type Target = _pyo3::PyRef<'a, #cls>;
}
impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a mut #cls
{
type Target = _pyo3::PyRefMut<'a, #cls>;
}
}
}
fn impl_into_py(&self) -> TokenStream {
let cls = self.cls;
let attr = self.attr;
// If #cls is not extended type, we allow Self->PyObject conversion
if !attr.has_extends {
quote! {
impl _pyo3::IntoPy<_pyo3::PyObject> for #cls {
fn into_py(self, py: _pyo3::Python) -> _pyo3::PyObject {
_pyo3::IntoPy::into_py(_pyo3::Py::new(py, self).unwrap(), py)
}
}
}
} else {
quote! {}
}
}
fn impl_pyclassimpl(&self) -> TokenStream {
let cls = self.cls;
let doc = self.doc.as_ref().map_or(quote! {"\0"}, |doc| quote! {#doc});
let is_gc = self.attr.is_gc;
let is_basetype = self.attr.is_basetype;
let base = &self.attr.base;
let is_subclass = self.attr.has_extends;
let thread_checker = if self.attr.has_unsendable {
quote! { _pyo3::class::impl_::ThreadCheckerImpl<#cls> }
} else if self.attr.has_extends {
quote! {
_pyo3::class::impl_::ThreadCheckerInherited<#cls, <#cls as _pyo3::class::impl_::PyClassImpl>::BaseType>
}
} else {
quote! { _pyo3::class::impl_::ThreadCheckerStub<#cls> }
};
let (for_each_py_method, methods_protos, inventory, inventory_class) = match self
.methods_type
{
PyClassMethodsType::Specialization => (
quote! { visitor(collector.py_methods()); },
quote! { visitor(collector.methods_protocol_slots()); },
None,
None,
),
PyClassMethodsType::Inventory => {
// To allow multiple #[pymethods] block, we define inventory types.
let inventory_class_name = syn::Ident::new(
&format!("Pyo3MethodsInventoryFor{}", cls.unraw()),
Span::call_site(),
);
(
quote! {
for inventory in _pyo3::inventory::iter::<<Self as _pyo3::class::impl_::PyClassImpl>::Inventory>() {
visitor(_pyo3::class::impl_::PyClassInventory::methods(inventory));
}
},
quote! {
for inventory in _pyo3::inventory::iter::<<Self as _pyo3::class::impl_::PyClassImpl>::Inventory>() {
visitor(_pyo3::class::impl_::PyClassInventory::slots(inventory));
}
},
Some(quote! { type Inventory = #inventory_class_name; }),
Some(define_inventory_class(&inventory_class_name)),
)
}
};
quote! {
impl _pyo3::class::impl_::PyClassImpl for #cls {
const DOC: &'static str = #doc;
const IS_GC: bool = #is_gc;
const IS_BASETYPE: bool = #is_basetype;
const IS_SUBCLASS: bool = #is_subclass;
type Layout = _pyo3::PyCell<Self>;
type BaseType = #base;
type ThreadChecker = #thread_checker;
#inventory
fn for_each_method_def(visitor: &mut dyn ::std::ops::FnMut(&[_pyo3::class::PyMethodDefType])) {
use _pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
#for_each_py_method;
visitor(collector.py_class_descriptors());
visitor(collector.object_protocol_methods());
visitor(collector.async_protocol_methods());
visitor(collector.descr_protocol_methods());
visitor(collector.mapping_protocol_methods());
visitor(collector.number_protocol_methods());
}
fn get_new() -> ::std::option::Option<_pyo3::ffi::newfunc> {
use _pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
collector.new_impl()
}
fn get_alloc() -> ::std::option::Option<_pyo3::ffi::allocfunc> {
use _pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
collector.alloc_impl()
}
fn get_free() -> ::std::option::Option<_pyo3::ffi::freefunc> {
use _pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
collector.free_impl()
}
fn for_each_proto_slot(visitor: &mut dyn ::std::ops::FnMut(&[_pyo3::ffi::PyType_Slot])) {
// Implementation which uses dtolnay specialization to load all slots.
use _pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
// This depends on Python implementation detail;
// an old slot entry will be overriden by newer ones.
visitor(collector.py_class_default_slots());
visitor(collector.object_protocol_slots());
visitor(collector.number_protocol_slots());
visitor(collector.iter_protocol_slots());
visitor(collector.gc_protocol_slots());
visitor(collector.descr_protocol_slots());
visitor(collector.mapping_protocol_slots());
visitor(collector.sequence_protocol_slots());
visitor(collector.async_protocol_slots());
visitor(collector.buffer_protocol_slots());
#methods_protos
}
fn get_buffer() -> ::std::option::Option<&'static _pyo3::class::impl_::PyBufferProcs> {
use _pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
collector.buffer_procs()
}
}
#inventory_class
}
}
fn impl_freelist(&self) -> TokenStream {
let cls = self.cls;
self.attr.freelist.as_ref().map_or(quote!{}, |freelist| {
quote! {
impl _pyo3::class::impl_::PyClassWithFreeList for #cls {
#[inline]
fn get_free_list(_py: _pyo3::Python<'_>) -> &mut _pyo3::impl_::freelist::FreeList<*mut _pyo3::ffi::PyObject> {
static mut FREELIST: *mut _pyo3::impl_::freelist::FreeList<*mut _pyo3::ffi::PyObject> = 0 as *mut _;
unsafe {
if FREELIST.is_null() {
FREELIST = ::std::boxed::Box::into_raw(::std::boxed::Box::new(
_pyo3::impl_::freelist::FreeList::with_capacity(#freelist)));
}
&mut *FREELIST
}
}
}
impl _pyo3::class::impl_::PyClassAllocImpl<#cls> for _pyo3::class::impl_::PyClassImplCollector<#cls> {
#[inline]
fn alloc_impl(self) -> ::std::option::Option<_pyo3::ffi::allocfunc> {
::std::option::Option::Some(_pyo3::class::impl_::alloc_with_freelist::<#cls>)
}
}
impl _pyo3::class::impl_::PyClassFreeImpl<#cls> for _pyo3::class::impl_::PyClassImplCollector<#cls> {
#[inline]
fn free_impl(self) -> ::std::option::Option<_pyo3::ffi::freefunc> {
::std::option::Option::Some(_pyo3::class::impl_::free_with_freelist::<#cls>)
}
}
}
})
}
/// Enforce at compile time that PyGCProtocol is implemented
fn impl_gc(&self) -> TokenStream {
let cls = self.cls;
let attr = self.attr;
if attr.is_gc {
let closure_name = format!("__assertion_closure_{}", cls);
let closure_token = syn::Ident::new(&closure_name, Span::call_site());
quote! {
fn #closure_token() {
use _pyo3::class;
fn _assert_implements_protocol<'p, T: _pyo3::class::PyGCProtocol<'p>>() {}
_assert_implements_protocol::<#cls>();
}
}
} else {
quote! {}
}
}
}
fn define_inventory_class(inventory_class_name: &syn::Ident) -> TokenStream {
quote! {
#[doc(hidden)]
pub struct #inventory_class_name {
methods: &'static [_pyo3::class::PyMethodDefType],
slots: &'static [_pyo3::ffi::PyType_Slot],
}
impl #inventory_class_name {
const fn new(
methods: &'static [_pyo3::class::PyMethodDefType],
slots: &'static [_pyo3::ffi::PyType_Slot],
) -> Self {
Self { methods, slots }
}
}
impl _pyo3::class::impl_::PyClassInventory for #inventory_class_name {
fn methods(&'static self) -> &'static [_pyo3::class::PyMethodDefType] {
self.methods
}
fn slots(&'static self) -> &'static [_pyo3::ffi::PyType_Slot] {
self.slots
}
}
// inventory requires these bounds
unsafe impl ::std::marker::Send for #inventory_class_name {}
unsafe impl ::std::marker::Sync for #inventory_class_name {}
_pyo3::inventory::collect!(#inventory_class_name);
}
}

View file

@ -2,14 +2,13 @@
use crate::{
attributes::{
self, get_deprecated_name_attribute, get_deprecated_text_signature_attribute,
get_pyo3_options, take_attributes, FromPyWithAttribute, NameAttribute,
TextSignatureAttribute,
self, get_pyo3_options, take_attributes, take_pyo3_options, CrateAttribute,
FromPyWithAttribute, NameAttribute, TextSignatureAttribute,
},
deprecations::Deprecations,
method::{self, CallingConvention, FnArg},
pymethod::check_generic,
utils::{self, ensure_not_async_fn},
utils::{self, ensure_not_async_fn, get_pyo3_crate},
};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
@ -240,17 +239,12 @@ pub struct PyFunctionOptions {
pub signature: Option<PyFunctionSignature>,
pub text_signature: Option<TextSignatureAttribute>,
pub deprecations: Deprecations,
pub krate: Option<CrateAttribute>,
}
impl Parse for PyFunctionOptions {
fn parse(input: ParseStream) -> Result<Self> {
let mut options = PyFunctionOptions {
pass_module: None,
name: None,
signature: None,
text_signature: None,
deprecations: Deprecations::new(),
};
let mut options = PyFunctionOptions::default();
while !input.is_empty() {
let lookahead = input.lookahead1();
@ -263,6 +257,9 @@ impl Parse for PyFunctionOptions {
if !input.is_empty() {
let _: Comma = input.parse()?;
}
} else if lookahead.peek(syn::Token![crate]) {
// TODO needs duplicate check?
options.krate = Some(input.parse()?);
} else {
// If not recognised attribute, this is "legacy" pyfunction syntax #[pyfunction(a, b)]
//
@ -281,6 +278,7 @@ pub enum PyFunctionOption {
PassModule(attributes::kw::pass_module),
Signature(PyFunctionSignature),
TextSignature(TextSignatureAttribute),
Crate(CrateAttribute),
}
impl Parse for PyFunctionOption {
@ -294,6 +292,8 @@ impl Parse for PyFunctionOption {
input.parse().map(PyFunctionOption::Signature)
} else if lookahead.peek(attributes::kw::text_signature) {
input.parse().map(PyFunctionOption::TextSignature)
} else if lookahead.peek(syn::Token![crate]) {
input.parse().map(PyFunctionOption::Crate)
} else {
Err(lookahead.error())
}
@ -303,34 +303,10 @@ 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_options(attrs)?;
options.add_attributes(take_pyo3_options(attrs)?)?;
Ok(options)
}
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_options(attr)? {
self.add_attributes(pyo3_attributes)?;
Ok(true)
} else if let Some(name) = get_deprecated_name_attribute(attr, &mut self.deprecations)?
{
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)
}
})?;
Ok(())
}
pub fn add_attributes(
&mut self,
attrs: impl IntoIterator<Item = PyFunctionOption>,
@ -360,6 +336,13 @@ impl PyFunctionOptions {
);
self.text_signature = Some(text_signature);
}
PyFunctionOption::Crate(path) => {
ensure_spanned!(
self.krate.is_none(),
path.0.span() => "`crate` may only be specified once"
);
self.krate = Some(path);
}
}
}
Ok(())
@ -379,7 +362,7 @@ pub fn build_py_function(
ast: &mut syn::ItemFn,
mut options: PyFunctionOptions,
) -> syn::Result<TokenStream> {
options.take_pyo3_options(&mut ast.attrs)?;
options.add_attributes(take_pyo3_options(&mut ast.attrs)?)?;
Ok(impl_wrap_pyfunction(ast, options)?.1)
}
@ -435,6 +418,7 @@ pub fn impl_wrap_pyfunction(
);
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
let krate = get_pyo3_crate(&options.krate);
let spec = method::FnSpec {
tp: if options.pass_module.is_some() {
@ -451,6 +435,7 @@ pub fn impl_wrap_pyfunction(
doc,
deprecations: options.deprecations,
text_signature: options.text_signature,
krate: krate.clone(),
};
let wrapper_ident = format_ident!("__pyo3_raw_{}", spec.name);
@ -459,10 +444,12 @@ pub fn impl_wrap_pyfunction(
let wrapped_pyfunction = quote! {
#wrapper
pub(crate) fn #function_wrapper_ident<'a>(
args: impl ::std::convert::Into<::pyo3::derive_utils::PyFunctionArguments<'a>>
) -> ::pyo3::PyResult<&'a ::pyo3::types::PyCFunction> {
::pyo3::types::PyCFunction::internal_new(#methoddef, args.into())
args: impl ::std::convert::Into<#krate::derive_utils::PyFunctionArguments<'a>>
) -> #krate::PyResult<&'a #krate::types::PyCFunction> {
use #krate as _pyo3;
_pyo3::types::PyCFunction::internal_new(#methoddef, args.into())
}
};
Ok((function_wrapper_ident, wrapped_pyfunction))

View file

@ -3,21 +3,72 @@
use std::collections::HashSet;
use crate::{
attributes::{take_pyo3_options, CrateAttribute},
konst::{ConstAttributes, ConstSpec},
pyfunction::PyFunctionOptions,
pymethod::{self, is_proto_method},
utils::get_pyo3_crate,
};
use proc_macro2::TokenStream;
use pymethod::GeneratedPyMethod;
use quote::quote;
use syn::spanned::Spanned;
use syn::{
parse::{Parse, ParseStream},
spanned::Spanned,
Result,
};
/// The mechanism used to collect `#[pymethods]` into the type object
#[derive(Copy, Clone)]
pub enum PyClassMethodsType {
Specialization,
Inventory,
}
enum PyImplPyO3Option {
Crate(CrateAttribute),
}
impl Parse for PyImplPyO3Option {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(syn::Token![crate]) {
input.parse().map(PyImplPyO3Option::Crate)
} else {
Err(lookahead.error())
}
}
}
#[derive(Default)]
pub struct PyImplOptions {
krate: Option<CrateAttribute>,
}
impl PyImplOptions {
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
let mut options: PyImplOptions = Default::default();
for option in take_pyo3_options(attrs)? {
match option {
PyImplPyO3Option::Crate(path) => options.set_crate(path)?,
}
}
Ok(options)
}
fn set_crate(&mut self, path: CrateAttribute) -> Result<()> {
ensure_spanned!(
self.krate.is_none(),
path.0.span() => "`crate` may only be specified once"
);
self.krate = Some(path);
Ok(())
}
}
pub fn build_py_methods(
ast: &mut syn::ItemImpl,
methods_type: PyClassMethodsType,
@ -30,7 +81,8 @@ pub fn build_py_methods(
"#[pymethods] cannot be used with lifetime parameters or generics"
);
} else {
impl_methods(&ast.self_ty, &mut ast.items, methods_type)
let options = PyImplOptions::from_attrs(&mut ast.attrs)?;
impl_methods(&ast.self_ty, &mut ast.items, methods_type, options)
}
}
@ -38,6 +90,7 @@ pub fn impl_methods(
ty: &syn::Type,
impls: &mut Vec<syn::ImplItem>,
methods_type: PyClassMethodsType,
options: PyImplOptions,
) -> syn::Result<TokenStream> {
let mut trait_impls = Vec::new();
let mut proto_impls = Vec::new();
@ -48,8 +101,9 @@ pub fn impl_methods(
for iimpl in impls.iter_mut() {
match iimpl {
syn::ImplItem::Method(meth) => {
let options = PyFunctionOptions::from_attrs(&mut meth.attrs)?;
match pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs, options)? {
let mut fun_options = PyFunctionOptions::from_attrs(&mut meth.attrs)?;
fun_options.krate = fun_options.krate.or_else(|| options.krate.clone());
match pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs, fun_options)? {
GeneratedPyMethod::Method(token_stream) => {
let attrs = get_cfg_attributes(&meth.attrs);
methods.push(quote!(#(#attrs)* #token_stream));
@ -94,42 +148,52 @@ pub fn impl_methods(
add_shared_proto_slots(ty, &mut proto_impls, implemented_proto_fragments);
let krate = get_pyo3_crate(&options.krate);
Ok(match methods_type {
PyClassMethodsType::Specialization => {
let methods_registration = impl_py_methods(ty, methods);
let protos_registration = impl_protos(ty, proto_impls);
quote! {
#(#trait_impls)*
const _: () = {
use #krate as _pyo3;
#protos_registration
#(#trait_impls)*
#methods_registration
#protos_registration
#methods_registration
};
}
}
PyClassMethodsType::Inventory => {
let inventory = submit_methods_inventory(ty, methods, proto_impls);
quote! {
#(#trait_impls)*
const _: () = {
use #krate as _pyo3;
#inventory
#(#trait_impls)*
#inventory
};
}
}
})
}
fn gen_py_const(cls: &syn::Type, spec: &ConstSpec) -> TokenStream {
pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec) -> TokenStream {
let member = &spec.rust_ident;
let deprecations = &spec.attributes.deprecations;
let python_name = &spec.null_terminated_python_name();
quote! {
::pyo3::class::PyMethodDefType::ClassAttribute({
::pyo3::class::PyClassAttributeDef::new(
_pyo3::class::PyMethodDefType::ClassAttribute({
_pyo3::class::PyClassAttributeDef::new(
#python_name,
::pyo3::class::methods::PyClassAttributeFactory({
fn __wrap(py: ::pyo3::Python<'_>) -> ::pyo3::PyObject {
_pyo3::class::methods::PyClassAttributeFactory({
fn __wrap(py: _pyo3::Python<'_>) -> _pyo3::PyObject {
#deprecations
::pyo3::IntoPy::into_py(#cls::#member, py)
_pyo3::IntoPy::into_py(#cls::#member, py)
}
__wrap
})
@ -138,13 +202,54 @@ fn gen_py_const(cls: &syn::Type, spec: &ConstSpec) -> TokenStream {
}
}
pub fn gen_default_slot_impls(cls: &syn::Ident, method_defs: Vec<TokenStream>) -> TokenStream {
// This function uses a lot of `unwrap()`; since method_defs are provided by us, they should
// all succeed.
let ty: syn::Type = syn::parse_quote!(#cls);
let mut method_defs: Vec<_> = method_defs
.into_iter()
.map(|token| syn::parse2::<syn::ImplItemMethod>(token).unwrap())
.collect();
let mut proto_impls = Vec::new();
for meth in &mut method_defs {
let options = PyFunctionOptions::from_attrs(&mut meth.attrs).unwrap();
match pymethod::gen_py_method(&ty, &mut meth.sig, &mut meth.attrs, options).unwrap() {
GeneratedPyMethod::Proto(token_stream) => {
let attrs = get_cfg_attributes(&meth.attrs);
proto_impls.push(quote!(#(#attrs)* #token_stream))
}
GeneratedPyMethod::SlotTraitImpl(..) => {
panic!("SlotFragment methods cannot have default implementation!")
}
GeneratedPyMethod::Method(_) | GeneratedPyMethod::TraitImpl(_) => {
panic!("Only protocol methods can have default implementation!")
}
}
}
quote! {
impl #cls {
#(#method_defs)*
}
impl ::pyo3::class::impl_::PyClassDefaultSlots<#cls>
for ::pyo3::class::impl_::PyClassImplCollector<#cls> {
fn py_class_default_slots(self) -> &'static [::pyo3::ffi::PyType_Slot] {
&[#(#proto_impls),*]
}
}
}
}
fn impl_py_methods(ty: &syn::Type, methods: Vec<TokenStream>) -> TokenStream {
quote! {
impl ::pyo3::class::impl_::PyMethods<#ty>
for ::pyo3::class::impl_::PyClassImplCollector<#ty>
impl _pyo3::class::impl_::PyMethods<#ty>
for _pyo3::class::impl_::PyClassImplCollector<#ty>
{
fn py_methods(self) -> &'static [::pyo3::class::methods::PyMethodDefType] {
static METHODS: &[::pyo3::class::methods::PyMethodDefType] = &[#(#methods),*];
fn py_methods(self) -> &'static [_pyo3::class::methods::PyMethodDefType] {
static METHODS: &[_pyo3::class::methods::PyMethodDefType] = &[#(#methods),*];
METHODS
}
}
@ -161,7 +266,7 @@ fn add_shared_proto_slots(
let first_implemented = implemented_proto_fragments.remove($first);
let second_implemented = implemented_proto_fragments.remove($second);
if first_implemented || second_implemented {
proto_impls.push(quote! { ::pyo3::$slot!(#ty) })
proto_impls.push(quote! { _pyo3::class::impl_::$slot!(#ty) })
}
}};
}
@ -193,10 +298,10 @@ fn add_shared_proto_slots(
fn impl_protos(ty: &syn::Type, proto_impls: Vec<TokenStream>) -> TokenStream {
quote! {
impl ::pyo3::class::impl_::PyMethodsProtocolSlots<#ty>
for ::pyo3::class::impl_::PyClassImplCollector<#ty>
impl _pyo3::class::impl_::PyMethodsProtocolSlots<#ty>
for _pyo3::class::impl_::PyClassImplCollector<#ty>
{
fn methods_protocol_slots(self) -> &'static [::pyo3::ffi::PyType_Slot] {
fn methods_protocol_slots(self) -> &'static [_pyo3::ffi::PyType_Slot] {
&[#(#proto_impls),*]
}
}
@ -209,11 +314,9 @@ fn submit_methods_inventory(
proto_impls: Vec<TokenStream>,
) -> TokenStream {
quote! {
::pyo3::inventory::submit! {
#![crate = ::pyo3] {
type Inventory = <#ty as ::pyo3::class::impl_::HasMethodsInventory>::Methods;
<Inventory as ::pyo3::class::impl_::PyMethodsInventory>::new(::std::vec![#(#methods),*], ::std::vec![#(#proto_impls),*])
}
_pyo3::inventory::submit! {
type Inventory = <#ty as _pyo3::class::impl_::PyClassImpl>::Inventory;
Inventory::new(&[#(#methods),*], &[#(#proto_impls),*])
}
}
}

View file

@ -120,12 +120,12 @@ pub fn gen_py_method(
(_, FnType::FnClass) => GeneratedPyMethod::Method(impl_py_method_def(
cls,
spec,
Some(quote!(::pyo3::ffi::METH_CLASS)),
Some(quote!(_pyo3::ffi::METH_CLASS)),
)?),
(_, FnType::FnStatic) => GeneratedPyMethod::Method(impl_py_method_def(
cls,
spec,
Some(quote!(::pyo3::ffi::METH_STATIC)),
Some(quote!(_pyo3::ffi::METH_STATIC)),
)?),
// special prototypes
(_, FnType::FnNew) => GeneratedPyMethod::TraitImpl(impl_py_method_def_new(cls, spec)?),
@ -186,7 +186,7 @@ pub fn impl_py_method_def(
};
let methoddef = spec.get_methoddef(quote! {{ #wrapper_def #wrapper_ident }});
Ok(quote! {
::pyo3::class::PyMethodDefType::#methoddef_type(#methoddef #add_flags)
_pyo3::class::PyMethodDefType::#methoddef_type(#methoddef #add_flags)
})
}
@ -194,8 +194,8 @@ fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream>
let wrapper_ident = syn::Ident::new("__wrap", Span::call_site());
let wrapper = spec.get_wrapper_function(&wrapper_ident, Some(cls))?;
Ok(quote! {
impl ::pyo3::class::impl_::PyClassNewImpl<#cls> for ::pyo3::class::impl_::PyClassImplCollector<#cls> {
fn new_impl(self) -> ::std::option::Option<::pyo3::ffi::newfunc> {
impl _pyo3::class::impl_::PyClassNewImpl<#cls> for _pyo3::class::impl_::PyClassImplCollector<#cls> {
fn new_impl(self) -> ::std::option::Option<_pyo3::ffi::newfunc> {
::std::option::Option::Some({
#wrapper
#wrapper_ident
@ -214,9 +214,9 @@ fn impl_call_slot(cls: &syn::Type, mut spec: FnSpec) -> Result<TokenStream> {
let wrapper = spec.get_wrapper_function(&wrapper_ident, Some(cls))?;
Ok(quote! {{
#wrapper
::pyo3::ffi::PyType_Slot {
slot: ::pyo3::ffi::Py_tp_call,
pfunc: __wrap as ::pyo3::ffi::ternaryfunc as _
_pyo3::ffi::PyType_Slot {
slot: _pyo3::ffi::Py_tp_call,
pfunc: __wrap as _pyo3::ffi::ternaryfunc as _
}
}})
}
@ -226,13 +226,13 @@ fn impl_py_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenStream {
let deprecations = &spec.deprecations;
let python_name = spec.null_terminated_python_name();
quote! {
::pyo3::class::PyMethodDefType::ClassAttribute({
::pyo3::class::PyClassAttributeDef::new(
_pyo3::class::PyMethodDefType::ClassAttribute({
_pyo3::class::PyClassAttributeDef::new(
#python_name,
::pyo3::class::methods::PyClassAttributeFactory({
fn __wrap(py: ::pyo3::Python<'_>) -> ::pyo3::PyObject {
_pyo3::class::methods::PyClassAttributeFactory({
fn __wrap(py: _pyo3::Python<'_>) -> _pyo3::PyObject {
#deprecations
::pyo3::IntoPy::into_py(#cls::#name(), py)
_pyo3::IntoPy::into_py(#cls::#name(), py)
}
__wrap
})
@ -295,26 +295,26 @@ pub fn impl_py_setter_def(cls: &syn::Type, property_type: PropertyType) -> Resul
}
};
Ok(quote! {
::pyo3::class::PyMethodDefType::Setter({
_pyo3::class::PyMethodDefType::Setter({
#deprecations
::pyo3::class::PySetterDef::new(
_pyo3::class::PySetterDef::new(
#python_name,
::pyo3::class::methods::PySetter({
_pyo3::class::methods::PySetter({
unsafe extern "C" fn __wrap(
_slf: *mut ::pyo3::ffi::PyObject,
_value: *mut ::pyo3::ffi::PyObject,
_slf: *mut _pyo3::ffi::PyObject,
_value: *mut _pyo3::ffi::PyObject,
_: *mut ::std::os::raw::c_void
) -> ::std::os::raw::c_int {
::pyo3::callback::handle_panic(|_py| {
_pyo3::callback::handle_panic(|_py| {
#slf
let _value = _py
.from_borrowed_ptr_or_opt(_value)
.ok_or_else(|| {
::pyo3::exceptions::PyAttributeError::new_err("can't delete attribute")
_pyo3::exceptions::PyAttributeError::new_err("can't delete attribute")
})?;
let _val = ::pyo3::FromPyObject::extract(_value)?;
let _val = _pyo3::FromPyObject::extract(_value)?;
::pyo3::callback::convert(_py, #setter_impl)
_pyo3::callback::convert(_py, #setter_impl)
})
}
__wrap
@ -375,18 +375,18 @@ pub fn impl_py_getter_def(cls: &syn::Type, property_type: PropertyType) -> Resul
}
};
Ok(quote! {
::pyo3::class::PyMethodDefType::Getter({
_pyo3::class::PyMethodDefType::Getter({
#deprecations
::pyo3::class::PyGetterDef::new(
_pyo3::class::PyGetterDef::new(
#python_name,
::pyo3::class::methods::PyGetter({
_pyo3::class::methods::PyGetter({
unsafe extern "C" fn __wrap(
_slf: *mut ::pyo3::ffi::PyObject,
_slf: *mut _pyo3::ffi::PyObject,
_: *mut ::std::os::raw::c_void
) -> *mut ::pyo3::ffi::PyObject {
::pyo3::callback::handle_panic(|_py| {
) -> *mut _pyo3::ffi::PyObject {
_pyo3::callback::handle_panic(|_py| {
#slf
::pyo3::callback::convert(_py, #getter_impl)
_pyo3::callback::convert(_py, #getter_impl)
})
}
__wrap
@ -399,14 +399,9 @@ pub fn impl_py_getter_def(cls: &syn::Type, property_type: PropertyType) -> Resul
/// Split an argument of pyo3::Python from the front of the arg list, if present
fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&FnArg>, &[FnArg]) {
if args
.get(0)
.map(|py| utils::is_python(py.ty))
.unwrap_or(false)
{
(Some(&args[0]), &args[1..])
} else {
(None, args)
match args {
[py, args @ ..] if utils::is_python(py.ty) => (Some(py), args),
args => (None, args),
}
}
@ -464,10 +459,10 @@ const __GETATTR__: SlotDef = SlotDef::new("Py_tp_getattro", "getattrofunc")
quote! {
// Behave like python's __getattr__ (as opposed to __getattribute__) and check
// for existing fields and methods first
let existing = ::pyo3::ffi::PyObject_GenericGetAttr(_slf, arg0);
let existing = _pyo3::ffi::PyObject_GenericGetAttr(_slf, arg0);
if existing.is_null() {
// PyObject_HasAttr also tries to get an object and clears the error if it fails
::pyo3::ffi::PyErr_Clear();
_pyo3::ffi::PyErr_Clear();
} else {
return existing;
}
@ -478,21 +473,21 @@ const __REPR__: SlotDef = SlotDef::new("Py_tp_repr", "reprfunc");
const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc")
.ret_ty(Ty::PyHashT)
.return_conversion(TokenGenerator(
|| quote! { ::pyo3::callback::HashCallbackOutput },
|| quote! { _pyo3::callback::HashCallbackOutput },
));
const __RICHCMP__: SlotDef = SlotDef::new("Py_tp_richcompare", "richcmpfunc")
.extract_error_mode(ExtractErrorMode::NotImplemented)
.arguments(&[Ty::Object, Ty::CompareOp]);
const __GET__: SlotDef =
SlotDef::new("Py_tp_descr_get", "descrgetfunc").arguments(&[Ty::Object, Ty::Object]);
const __GET__: SlotDef = SlotDef::new("Py_tp_descr_get", "descrgetfunc")
.arguments(&[Ty::MaybeNullObject, Ty::MaybeNullObject]);
const __ITER__: SlotDef = SlotDef::new("Py_tp_iter", "getiterfunc");
const __NEXT__: SlotDef = SlotDef::new("Py_tp_iternext", "iternextfunc").return_conversion(
TokenGenerator(|| quote! { ::pyo3::class::iter::IterNextOutput::<_, _> }),
TokenGenerator(|| quote! { _pyo3::class::iter::IterNextOutput::<_, _> }),
);
const __AWAIT__: SlotDef = SlotDef::new("Py_am_await", "unaryfunc");
const __AITER__: SlotDef = SlotDef::new("Py_am_aiter", "unaryfunc");
const __ANEXT__: SlotDef = SlotDef::new("Py_am_anext", "unaryfunc").return_conversion(
TokenGenerator(|| quote! { ::pyo3::class::pyasync::IterANextOutput::<_, _> }),
TokenGenerator(|| quote! { _pyo3::class::pyasync::IterANextOutput::<_, _> }),
);
const __LEN__: SlotDef = SlotDef::new("Py_mp_length", "lenfunc").ret_ty(Ty::PySsizeT);
const __CONTAINS__: SlotDef = SlotDef::new("Py_sq_contains", "objobjproc")
@ -606,6 +601,7 @@ fn pyproto(method_name: &str) -> Option<&'static SlotDef> {
#[derive(Clone, Copy)]
enum Ty {
Object,
MaybeNullObject,
NonNullObject,
CompareOp,
Int,
@ -617,11 +613,11 @@ enum Ty {
impl Ty {
fn ffi_type(self) -> TokenStream {
match self {
Ty::Object => quote! { *mut ::pyo3::ffi::PyObject },
Ty::NonNullObject => quote! { ::std::ptr::NonNull<::pyo3::ffi::PyObject> },
Ty::Object | Ty::MaybeNullObject => quote! { *mut _pyo3::ffi::PyObject },
Ty::NonNullObject => quote! { ::std::ptr::NonNull<_pyo3::ffi::PyObject> },
Ty::Int | Ty::CompareOp => quote! { ::std::os::raw::c_int },
Ty::PyHashT => quote! { ::pyo3::ffi::Py_hash_t },
Ty::PySsizeT => quote! { ::pyo3::ffi::Py_ssize_t },
Ty::PyHashT => quote! { _pyo3::ffi::Py_hash_t },
Ty::PySsizeT => quote! { _pyo3::ffi::Py_ssize_t },
Ty::Void => quote! { () },
}
}
@ -640,7 +636,23 @@ impl Ty {
extract_error_mode,
py,
quote! {
#py.from_borrowed_ptr::<::pyo3::PyAny>(#ident).extract()
#py.from_borrowed_ptr::<_pyo3::PyAny>(#ident).extract()
},
);
extract_object(cls, arg.ty, ident, extract)
}
Ty::MaybeNullObject => {
let extract = handle_error(
extract_error_mode,
py,
quote! {
#py.from_borrowed_ptr::<_pyo3::PyAny>(
if #ident.is_null() {
_pyo3::ffi::Py_None()
} else {
#ident
}
).extract()
},
);
extract_object(cls, arg.ty, ident, extract)
@ -650,7 +662,7 @@ impl Ty {
extract_error_mode,
py,
quote! {
#py.from_borrowed_ptr::<::pyo3::PyAny>(#ident.as_ptr()).extract()
#py.from_borrowed_ptr::<_pyo3::PyAny>(#ident.as_ptr()).extract()
},
);
extract_object(cls, arg.ty, ident, extract)
@ -660,8 +672,8 @@ impl Ty {
extract_error_mode,
py,
quote! {
::pyo3::class::basic::CompareOp::from_raw(#ident)
.ok_or_else(|| ::pyo3::exceptions::PyValueError::new_err("invalid comparison operator"))
_pyo3::class::basic::CompareOp::from_raw(#ident)
.ok_or_else(|| _pyo3::exceptions::PyValueError::new_err("invalid comparison operator"))
},
);
quote! {
@ -683,7 +695,7 @@ fn handle_error(
ExtractErrorMode::NotImplemented => quote! {
match #extract {
::std::result::Result::Ok(value) => value,
::std::result::Result::Err(_) => { return ::pyo3::callback::convert(#py, #py.NotImplemented()); },
::std::result::Result::Err(_) => { return _pyo3::callback::convert(#py, #py.NotImplemented()); },
}
},
}
@ -700,7 +712,7 @@ fn extract_object(
replace_self(&mut tref.elem, cls);
let mut_ = tref.mutability;
quote! {
let #mut_ #ident: <#tref as ::pyo3::derive_utils::ExtractExt<'_>>::Target = #extract;
let #mut_ #ident: <#tref as _pyo3::derive_utils::ExtractExt<'_>>::Target = #extract;
let #ident = &#mut_ *#ident;
}
} else {
@ -719,13 +731,13 @@ impl ReturnMode {
fn return_call_output(&self, py: &syn::Ident, call: TokenStream) -> TokenStream {
match self {
ReturnMode::Conversion(conversion) => quote! {
let _result: ::pyo3::PyResult<#conversion> = #call;
::pyo3::callback::convert(#py, _result)
let _result: _pyo3::PyResult<#conversion> = #call;
_pyo3::callback::convert(#py, _result)
},
ReturnMode::ReturnSelf => quote! {
let _result: ::pyo3::PyResult<()> = #call;
let _result: _pyo3::PyResult<()> = #call;
_result?;
::pyo3::ffi::Py_XINCREF(_raw_slf);
_pyo3::ffi::Py_XINCREF(_raw_slf);
::std::result::Result::Ok(_raw_slf)
},
}
@ -809,16 +821,16 @@ impl SlotDef {
return_mode.as_ref(),
)?;
Ok(quote!({
unsafe extern "C" fn __wrap(_raw_slf: *mut ::pyo3::ffi::PyObject, #(#method_arguments),*) -> #ret_ty {
unsafe extern "C" fn __wrap(_raw_slf: *mut _pyo3::ffi::PyObject, #(#method_arguments),*) -> #ret_ty {
let _slf = _raw_slf;
#before_call_method
::pyo3::callback::handle_panic(|#py| {
_pyo3::callback::handle_panic(|#py| {
#body
})
}
::pyo3::ffi::PyType_Slot {
slot: ::pyo3::ffi::#slot,
pfunc: __wrap as ::pyo3::ffi::#func_ty as _
_pyo3::ffi::PyType_Slot {
slot: _pyo3::ffi::#slot,
pfunc: __wrap as _pyo3::ffi::#func_ty as _
}
}))
}
@ -846,7 +858,7 @@ fn generate_method_body(
let rust_name = spec.name;
let (arg_idents, conversions) =
extract_proto_arguments(cls, py, &spec.args, arguments, extract_error_mode)?;
let call = quote! { ::pyo3::callback::convert(#py, #cls::#rust_name(_slf, #(#arg_idents),*)) };
let call = quote! { _pyo3::callback::convert(#py, #cls::#rust_name(_slf, #(#arg_idents),*)) };
let body = if let Some(return_mode) = return_mode {
return_mode.return_call_output(py, call)
} else {
@ -900,15 +912,15 @@ impl SlotFragmentDef {
let body = generate_method_body(cls, spec, &py, arguments, *extract_error_mode, None)?;
let ret_ty = ret_ty.ffi_type();
Ok(quote! {
impl ::pyo3::class::impl_::#fragment_trait<#cls> for ::pyo3::class::impl_::PyClassImplCollector<#cls> {
impl _pyo3::class::impl_::#fragment_trait<#cls> for _pyo3::class::impl_::PyClassImplCollector<#cls> {
#[inline]
unsafe fn #method(
self,
#py: ::pyo3::Python,
_raw_slf: *mut ::pyo3::ffi::PyObject,
#py: _pyo3::Python,
_raw_slf: *mut _pyo3::ffi::PyObject,
#(#method_arguments),*
) -> ::pyo3::PyResult<#ret_ty> {
) -> _pyo3::PyResult<#ret_ty> {
let _slf = _raw_slf;
#body
}

View file

@ -18,7 +18,6 @@ pub fn build_py_proto(ast: &mut syn::ItemImpl) -> syn::Result<TokenStream> {
Some(segment) if segment.ident == "PyAsyncProtocol" => &defs::ASYNC,
Some(segment) if segment.ident == "PyMappingProtocol" => &defs::MAPPING,
Some(segment) if segment.ident == "PyIterProtocol" => &defs::ITER,
Some(segment) if segment.ident == "PyContextProtocol" => &defs::CONTEXT,
Some(segment) if segment.ident == "PySequenceProtocol" => &defs::SEQ,
Some(segment) if segment.ident == "PyNumberProtocol" => &defs::NUM,
Some(segment) if segment.ident == "PyDescrProtocol" => &defs::DESCR,
@ -68,7 +67,7 @@ fn impl_proto_impl(
let flags = if m.can_coexist {
// We need METH_COEXIST here to prevent __add__ from overriding __radd__
Some(quote!(::pyo3::ffi::METH_COEXIST))
Some(quote!(_pyo3::ffi::METH_COEXIST))
} else {
None
};
@ -87,10 +86,14 @@ fn impl_proto_impl(
}
let normal_methods = impl_normal_methods(py_methods, ty, proto);
let protocol_methods = impl_proto_methods(method_names, ty, proto);
Ok(quote! {
#trait_impls
#normal_methods
#protocol_methods
const _: () = {
use ::pyo3 as _pyo3; // pyproto doesn't support specifying #[pyo3(crate)]
#trait_impls
#normal_methods
#protocol_methods
};
})
}
@ -106,11 +109,11 @@ fn impl_normal_methods(
let methods_trait = proto.methods_trait();
let methods_trait_methods = proto.methods_trait_methods();
quote! {
impl ::pyo3::class::impl_::#methods_trait<#ty>
for ::pyo3::class::impl_::PyClassImplCollector<#ty>
impl _pyo3::class::impl_::#methods_trait<#ty>
for _pyo3::class::impl_::PyClassImplCollector<#ty>
{
fn #methods_trait_methods(self) -> &'static [::pyo3::class::methods::PyMethodDefType] {
static METHODS: &[::pyo3::class::methods::PyMethodDefType] =
fn #methods_trait_methods(self) -> &'static [_pyo3::class::methods::PyMethodDefType] {
static METHODS: &[_pyo3::class::methods::PyMethodDefType] =
&[#(#py_methods),*];
METHODS
}
@ -139,16 +142,16 @@ fn impl_proto_methods(
if build_config.version <= PY39 && proto.name == "Buffer" {
maybe_buffer_methods = Some(quote! {
impl ::pyo3::class::impl_::PyBufferProtocolProcs<#ty>
for ::pyo3::class::impl_::PyClassImplCollector<#ty>
impl _pyo3::class::impl_::PyBufferProtocolProcs<#ty>
for _pyo3::class::impl_::PyClassImplCollector<#ty>
{
fn buffer_procs(
self
) -> ::std::option::Option<&'static ::pyo3::class::impl_::PyBufferProcs> {
static PROCS: ::pyo3::class::impl_::PyBufferProcs
= ::pyo3::class::impl_::PyBufferProcs {
bf_getbuffer: ::std::option::Option::Some(::pyo3::class::buffer::getbuffer::<#ty>),
bf_releasebuffer: ::std::option::Option::Some(::pyo3::class::buffer::releasebuffer::<#ty>),
) -> ::std::option::Option<&'static _pyo3::class::impl_::PyBufferProcs> {
static PROCS: _pyo3::class::impl_::PyBufferProcs
= _pyo3::class::impl_::PyBufferProcs {
bf_getbuffer: ::std::option::Option::Some(_pyo3::class::buffer::getbuffer::<#ty>),
bf_releasebuffer: ::std::option::Option::Some(_pyo3::class::buffer::releasebuffer::<#ty>),
};
::std::option::Option::Some(&PROCS)
}
@ -162,8 +165,8 @@ fn impl_proto_methods(
let slot = syn::Ident::new(def.slot, Span::call_site());
let slot_impl = syn::Ident::new(def.slot_impl, Span::call_site());
quote! {{
::pyo3::ffi::PyType_Slot {
slot: ::pyo3::ffi::#slot,
_pyo3::ffi::PyType_Slot {
slot: _pyo3::ffi::#slot,
pfunc: #module::#slot_impl::<#ty> as _
}
}}
@ -177,10 +180,10 @@ fn impl_proto_methods(
quote! {
#maybe_buffer_methods
impl ::pyo3::class::impl_::#slots_trait<#ty>
for ::pyo3::class::impl_::PyClassImplCollector<#ty>
impl _pyo3::class::impl_::#slots_trait<#ty>
for _pyo3::class::impl_::PyClassImplCollector<#ty>
{
fn #slots_trait_slots(self) -> &'static [::pyo3::ffi::PyType_Slot] {
fn #slots_trait_slots(self) -> &'static [_pyo3::ffi::PyType_Slot] {
&[#(#tokens),*]
}
}

View file

@ -3,7 +3,7 @@ use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::spanned::Spanned;
use crate::attributes::TextSignatureAttribute;
use crate::attributes::{CrateAttribute, TextSignatureAttribute};
/// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span.
macro_rules! err_spanned {
@ -62,8 +62,6 @@ pub fn option_type_argument(ty: &syn::Type) -> Option<&syn::Type> {
#[derive(Clone)]
pub struct PythonDoc(TokenStream);
// TODO(#1782) use strip_prefix on Rust 1.45 or greater
#[allow(clippy::manual_strip)]
/// Collects all #[doc = "..."] attributes into a TokenStream evaluating to a null-terminated string
/// e.g. concat!("...", "\n", "\0")
pub fn get_doc(
@ -79,11 +77,7 @@ pub fn get_doc(
syn::token::Bracket(Span::call_site()).surround(&mut tokens, |tokens| {
if let Some((python_name, text_signature)) = text_signature {
// create special doc string lines to set `__text_signature__`
let signature_lines = format!(
"{}{}\n--\n\n",
python_name.to_string(),
text_signature.lit.value()
);
let signature_lines = format!("{}{}\n--\n\n", python_name, text_signature.lit.value());
signature_lines.to_tokens(tokens);
comma.to_tokens(tokens);
}
@ -107,11 +101,11 @@ pub fn get_doc(
// Strip single left space from literal strings, if needed.
// e.g. `/// Hello world` expands to #[doc = " Hello world"]
let doc_line = lit_str.value();
if doc_line.starts_with(' ') {
syn::LitStr::new(&doc_line[1..], lit_str.span()).to_tokens(tokens)
} else {
lit_str.to_tokens(tokens)
}
doc_line
.strip_prefix(' ')
.map(|stripped| syn::LitStr::new(stripped, lit_str.span()))
.unwrap_or(lit_str)
.to_tokens(tokens);
} else {
// This is probably a macro doc from Rust 1.54, e.g. #[doc = include_str!(...)]
token_stream.to_tokens(tokens)
@ -195,3 +189,10 @@ pub(crate) fn replace_self(ty: &mut syn::Type, cls: &syn::Type) {
_ => {}
}
}
/// Extract the path to the pyo3 crate, or use the default (`::pyo3`).
pub(crate) fn get_pyo3_crate(attr: &Option<CrateAttribute>) -> syn::Path {
attr.as_ref()
.map(|p| p.0.clone())
.unwrap_or_else(|| syn::parse_str("::pyo3").unwrap())
}

View file

@ -1,6 +1,6 @@
[package]
name = "pyo3-macros"
version = "0.15.0"
version = "0.15.1"
description = "Proc macros for PyO3 package"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"]
@ -19,4 +19,4 @@ multiple-pymethods = []
[dependencies]
quote = "1"
syn = { version = "1", features = ["full", "extra-traits"] }
pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.15.0" }
pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.15.1" }

View file

@ -7,12 +7,12 @@ extern crate proc_macro;
use proc_macro::TokenStream;
use pyo3_macros_backend::{
build_derive_from_pyobject, build_py_class, build_py_function, build_py_methods,
build_derive_from_pyobject, build_py_class, build_py_enum, build_py_function, build_py_methods,
build_py_proto, get_doc, process_functions_in_module, py_init, PyClassArgs, PyClassMethodsType,
PyFunctionOptions, PyModuleOptions,
};
use quote::quote;
use syn::parse_macro_input;
use syn::{parse::Nothing, parse_macro_input};
/// A proc macro used to implement Python modules.
///
@ -31,19 +31,11 @@ use syn::parse_macro_input;
///
/// [1]: https://pyo3.rs/latest/module.html
#[proc_macro_attribute]
pub fn pymodule(attr: TokenStream, input: TokenStream) -> TokenStream {
pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream {
parse_macro_input!(args as Nothing);
let mut ast = parse_macro_input!(input as syn::ItemFn);
let deprecated_pymodule_name_arg = if attr.is_empty() {
None
} else {
Some(parse_macro_input!(attr as syn::Ident))
};
let options = match PyModuleOptions::from_pymodule_arg_and_attrs(
deprecated_pymodule_name_arg,
&mut ast.attrs,
) {
let options = match PyModuleOptions::from_attrs(&mut ast.attrs) {
Ok(options) => options,
Err(e) => return e.to_compile_error().into(),
};
@ -115,12 +107,17 @@ pub fn pyproto(_: TokenStream, input: TokenStream) -> TokenStream {
/// [10]: https://en.wikipedia.org/wiki/Free_list
#[proc_macro_attribute]
pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream {
let methods_type = if cfg!(feature = "multiple-pymethods") {
PyClassMethodsType::Inventory
} else {
PyClassMethodsType::Specialization
};
pyclass_impl(attr, input, methods_type)
use syn::Item;
let item = parse_macro_input!(input as Item);
match item {
Item::Struct(struct_) => pyclass_impl(attr, struct_, methods_type()),
Item::Enum(enum_) => pyclass_enum_impl(attr, enum_, methods_type()),
unsupported => {
syn::Error::new_spanned(unsupported, "#[pyclass] only supports structs and enums.")
.to_compile_error()
.into()
}
}
}
/// A proc macro used to expose methods to Python.
@ -203,12 +200,11 @@ pub fn derive_from_py_object(item: TokenStream) -> TokenStream {
}
fn pyclass_impl(
attr: TokenStream,
input: TokenStream,
attrs: TokenStream,
mut ast: syn::ItemStruct,
methods_type: PyClassMethodsType,
) -> TokenStream {
let mut ast = parse_macro_input!(input as syn::ItemStruct);
let args = parse_macro_input!(attr as PyClassArgs);
let args = parse_macro_input!(attrs with PyClassArgs::parse_stuct_args);
let expanded =
build_py_class(&mut ast, &args, methods_type).unwrap_or_else(|e| e.to_compile_error());
@ -219,6 +215,22 @@ fn pyclass_impl(
.into()
}
fn pyclass_enum_impl(
attrs: TokenStream,
mut ast: syn::ItemEnum,
methods_type: PyClassMethodsType,
) -> TokenStream {
let args = parse_macro_input!(attrs with PyClassArgs::parse_enum_args);
let expanded =
build_py_enum(&mut ast, &args, methods_type).unwrap_or_else(|e| e.into_compile_error());
quote!(
#ast
#expanded
)
.into()
}
fn pymethods_impl(input: TokenStream, methods_type: PyClassMethodsType) -> TokenStream {
let mut ast = parse_macro_input!(input as syn::ItemImpl);
let expanded =
@ -230,3 +242,11 @@ fn pymethods_impl(input: TokenStream, methods_type: PyClassMethodsType) -> Token
)
.into()
}
fn methods_type() -> PyClassMethodsType {
if cfg!(feature = "multiple-pymethods") {
PyClassMethodsType::Inventory
} else {
PyClassMethodsType::Specialization
}
}

View file

@ -75,12 +75,7 @@ impl ElementType {
pub fn from_format(format: &CStr) -> ElementType {
match format.to_bytes() {
[char] | [b'@', char] => native_element_type_from_type_char(*char),
[modifier, char]
if (*modifier == b'='
|| *modifier == b'<'
|| *modifier == b'>'
|| *modifier == b'!') =>
{
[modifier, char] if matches!(modifier, b'=' | b'<' | b'>' | b'!') => {
standard_element_type_from_type_char(*char)
}
_ => ElementType::Unknown,
@ -168,6 +163,10 @@ fn is_matching_endian(c: u8) -> bool {
}
/// Trait implemented for possible element types of `PyBuffer`.
///
/// # Safety
///
/// This trait must only be implemented for types which represent valid elements of Python buffers.
pub unsafe trait Element: Copy {
/// Gets whether the element specified in the format string is potentially compatible.
/// Alignment and size are checked separately from this function.

View file

@ -81,17 +81,6 @@ pub trait PyObjectProtocol<'p>: PyClass {
unimplemented!()
}
#[deprecated(
since = "0.14.0",
note = "prefer implementing `__format__` in `#[pymethods]` instead of in a protocol"
)]
fn __format__(&'p self, format_spec: Self::Format) -> Self::Result
where
Self: PyObjectFormatProtocol<'p>,
{
unimplemented!()
}
fn __hash__(&'p self) -> Self::Result
where
Self: PyObjectHashProtocol<'p>,
@ -99,17 +88,6 @@ pub trait PyObjectProtocol<'p>: PyClass {
unimplemented!()
}
#[deprecated(
since = "0.14.0",
note = "prefer implementing `__bytes__` in `#[pymethods]` instead of in a protocol"
)]
fn __bytes__(&'p self) -> Self::Result
where
Self: PyObjectBytesProtocol<'p>,
{
unimplemented!()
}
fn __richcmp__(&'p self, other: Self::Other, op: CompareOp) -> Self::Result
where
Self: PyObjectRichcmpProtocol<'p>,
@ -143,19 +121,12 @@ pub trait PyObjectStrProtocol<'p>: PyObjectProtocol<'p> {
pub trait PyObjectReprProtocol<'p>: PyObjectProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>;
}
pub trait PyObjectFormatProtocol<'p>: PyObjectProtocol<'p> {
type Format: FromPyObject<'p>;
type Result: IntoPyCallbackOutput<PyObject>;
}
pub trait PyObjectHashProtocol<'p>: PyObjectProtocol<'p> {
type Result: IntoPyCallbackOutput<HashCallbackOutput>;
}
pub trait PyObjectBoolProtocol<'p>: PyObjectProtocol<'p> {
type Result: IntoPyCallbackOutput<bool>;
}
pub trait PyObjectBytesProtocol<'p>: PyObjectProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>;
}
pub trait PyObjectRichcmpProtocol<'p>: PyObjectProtocol<'p> {
type Other: FromPyObject<'p>;
type Result: IntoPyCallbackOutput<PyObject>;

View file

@ -1,49 +0,0 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
//! Context manager api
//! Trait and support implementation for context manager api
use crate::callback::IntoPyCallbackOutput;
use crate::{PyClass, PyObject};
/// Context manager interface
#[allow(unused_variables)]
pub trait PyContextProtocol<'p>: PyClass {
#[deprecated(
since = "0.14.0",
note = "prefer implementing `__enter__` in `#[pymethods]` instead of in a protocol"
)]
fn __enter__(&'p mut self) -> Self::Result
where
Self: PyContextEnterProtocol<'p>,
{
unimplemented!()
}
#[deprecated(
since = "0.14.0",
note = "prefer implementing `__exit__` in `#[pymethods]` instead of in a protocol"
)]
fn __exit__(
&'p mut self,
exc_type: Option<Self::ExcType>,
exc_value: Option<Self::ExcValue>,
traceback: Option<Self::Traceback>,
) -> Self::Result
where
Self: PyContextExitProtocol<'p>,
{
unimplemented!()
}
}
pub trait PyContextEnterProtocol<'p>: PyContextProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>;
}
pub trait PyContextExitProtocol<'p>: PyContextProtocol<'p> {
type ExcType: crate::FromPyObject<'p>;
type ExcValue: crate::FromPyObject<'p>;
type Traceback: crate::FromPyObject<'p>;
type Result: IntoPyCallbackOutput<PyObject>;
}

View file

@ -6,7 +6,6 @@
//! https://docs.python.org/3/reference/datamodel.html#implementing-descriptors)
use crate::callback::IntoPyCallbackOutput;
use crate::types::PyAny;
use crate::{FromPyObject, PyClass, PyObject};
use std::os::raw::c_int;
@ -30,28 +29,6 @@ pub trait PyDescrProtocol<'p>: PyClass {
{
unimplemented!()
}
#[deprecated(
since = "0.14.0",
note = "prefer implementing `__delete__` in `#[pymethods]` instead of in a protocol"
)]
fn __delete__(&'p self, instance: &'p PyAny) -> Self::Result
where
Self: PyDescrDeleteProtocol<'p>,
{
unimplemented!()
}
#[deprecated(
since = "0.14.0",
note = "prefer implementing `__set_name__` in `#[pymethods]` instead of in a protocol"
)]
fn __set_name__(&'p self, instance: &'p PyAny) -> Self::Result
where
Self: PyDescrSetNameProtocol<'p>,
{
unimplemented!()
}
}
pub trait PyDescrGetProtocol<'p>: PyDescrProtocol<'p> {
@ -68,15 +45,5 @@ pub trait PyDescrSetProtocol<'p>: PyDescrProtocol<'p> {
type Result: IntoPyCallbackOutput<()>;
}
pub trait PyDescrDeleteProtocol<'p>: PyDescrProtocol<'p> {
type Inst: FromPyObject<'p>;
type Result: IntoPyCallbackOutput<()>;
}
pub trait PyDescrSetNameProtocol<'p>: PyDescrProtocol<'p> {
type Inst: FromPyObject<'p>;
type Result: IntoPyCallbackOutput<()>;
}
py_ternarys_func!(descr_get, PyDescrGetProtocol, Self::__get__);
py_ternarys_func!(descr_set, PyDescrSetProtocol, Self::__set__, c_int);

View file

@ -67,6 +67,9 @@ pub trait PyClassImpl: Sized {
/// can be accessed by multiple threads by `threading` module.
type ThreadChecker: PyClassThreadChecker<Self>;
#[cfg(feature = "multiple-pymethods")]
type Inventory: PyClassInventory;
fn for_each_method_def(_visitor: &mut dyn FnMut(&[PyMethodDefType])) {}
fn get_new() -> Option<ffi::newfunc> {
None
@ -180,6 +183,7 @@ macro_rules! define_pyclass_setattr_slot {
}
}};
}
pub use $generate_macro;
};
}
@ -272,7 +276,7 @@ macro_rules! define_pyclass_binary_operator_slot {
_other: *mut $crate::ffi::PyObject,
) -> *mut $crate::ffi::PyObject {
$crate::callback::handle_panic(|py| {
use ::pyo3::class::impl_::*;
use $crate::class::impl_::*;
let collector = PyClassImplCollector::<$cls>::new();
let lhs_result = collector.$lhs(py, _slf, _other)?;
if lhs_result == $crate::ffi::Py_NotImplemented() {
@ -289,6 +293,7 @@ macro_rules! define_pyclass_binary_operator_slot {
}
}};
}
pub use $generate_macro;
};
}
@ -464,7 +469,7 @@ macro_rules! generate_pyclass_pow_slot {
_mod: *mut $crate::ffi::PyObject,
) -> *mut $crate::ffi::PyObject {
$crate::callback::handle_panic(|py| {
use ::pyo3::class::impl_::*;
use $crate::class::impl_::*;
let collector = PyClassImplCollector::<$cls>::new();
let lhs_result = collector.__pow__(py, _slf, _other, _mod)?;
if lhs_result == $crate::ffi::Py_NotImplemented() {
@ -481,6 +486,7 @@ macro_rules! generate_pyclass_pow_slot {
}
}};
}
pub use generate_pyclass_pow_slot;
pub trait PyClassAllocImpl<T> {
fn alloc_impl(self) -> Option<ffi::allocfunc>;
@ -542,7 +548,6 @@ pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
/// # Safety
/// - `obj` must be a valid pointer to an instance of T (not a subclass).
/// - The GIL must be held.
#[allow(clippy::collapsible_if)] // for if cfg!
pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
let obj = obj as *mut ffi::PyObject;
debug_assert_eq!(
@ -560,10 +565,9 @@ pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_
};
free(obj as *mut c_void);
if cfg!(Py_3_8) {
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
}
#[cfg(Py_3_8)]
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
}
}
}
@ -609,11 +613,8 @@ macro_rules! methods_trait {
/// Method storage for `#[pyclass]`.
/// Allows arbitrary `#[pymethod]` blocks to submit their methods,
/// which are eventually collected by `#[pyclass]`.
#[cfg(all(feature = "macros", feature = "multiple-pymethods"))]
pub trait PyMethodsInventory: inventory::Collect {
/// Create a new instance
fn new(methods: Vec<PyMethodDefType>, slots: Vec<ffi::PyType_Slot>) -> Self;
#[cfg(feature = "multiple-pymethods")]
pub trait PyClassInventory: inventory::Collect {
/// Returns the methods for a single `#[pymethods] impl` block
fn methods(&'static self) -> &'static [PyMethodDefType];
@ -621,13 +622,6 @@ pub trait PyMethodsInventory: inventory::Collect {
fn slots(&'static self) -> &'static [ffi::PyType_Slot];
}
/// Implemented for `#[pyclass]` in our proc macro code.
/// Indicates that the pyclass has its own method storage.
#[cfg(all(feature = "macros", feature = "multiple-pymethods"))]
pub trait HasMethodsInventory {
type Methods: PyMethodsInventory;
}
// Methods from #[pyo3(get, set)] on struct fields.
methods_trait!(PyClassDescriptors, py_class_descriptors);
@ -666,13 +660,15 @@ slots_trait!(PyAsyncProtocolSlots, async_protocol_slots);
slots_trait!(PySequenceProtocolSlots, sequence_protocol_slots);
slots_trait!(PyBufferProtocolSlots, buffer_protocol_slots);
// slots that PyO3 implements by default, but can be overidden by the users.
slots_trait!(PyClassDefaultSlots, py_class_default_slots);
// Protocol slots from #[pymethods] if not using inventory.
#[cfg(not(feature = "multiple-pymethods"))]
slots_trait!(PyMethodsProtocolSlots, methods_protocol_slots);
methods_trait!(PyObjectProtocolMethods, object_protocol_methods);
methods_trait!(PyAsyncProtocolMethods, async_protocol_methods);
methods_trait!(PyContextProtocolMethods, context_protocol_methods);
methods_trait!(PyDescrProtocolMethods, descr_protocol_methods);
methods_trait!(PyMappingProtocolMethods, mapping_protocol_methods);
methods_trait!(PyNumberProtocolMethods, number_protocol_methods);

View file

@ -36,17 +36,6 @@ pub trait PyMappingProtocol<'p>: PyClass {
{
unimplemented!()
}
#[deprecated(
since = "0.14.0",
note = "prefer implementing `__reversed__` in `#[pymethods]` instead of in a protocol"
)]
fn __reversed__(&'p self) -> Self::Result
where
Self: PyMappingReversedProtocol<'p>,
{
unimplemented!()
}
}
// The following are a bunch of marker traits used to detect
@ -72,10 +61,6 @@ pub trait PyMappingDelItemProtocol<'p>: PyMappingProtocol<'p> {
type Result: IntoPyCallbackOutput<()>;
}
pub trait PyMappingReversedProtocol<'p>: PyMappingProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>;
}
py_len_func!(len, PyMappingLenProtocol, Self::__len__);
py_binary_func!(getitem, PyMappingGetItemProtocol, Self::__getitem__);
py_func_set!(setitem, PyMappingSetItemProtocol, Self::__setitem__);

View file

@ -28,7 +28,7 @@ pub enum PyMethodDefType {
pub enum PyMethodType {
PyCFunction(PyCFunction),
PyCFunctionWithKeywords(PyCFunctionWithKeywords),
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
#[cfg(not(Py_LIMITED_API))]
PyCFunctionFastWithKeywords(PyCFunctionFastWithKeywords),
}
@ -38,7 +38,7 @@ 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)))]
#[cfg(not(Py_LIMITED_API))]
#[derive(Clone, Copy, Debug)]
pub struct PyCFunctionFastWithKeywords(pub ffi::_PyCFunctionFastWithKeywords);
#[derive(Clone, Copy, Debug)]
@ -111,7 +111,7 @@ impl PyMethodDef {
}
/// Define a function that can take `*args` and `**kwargs`.
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
#[cfg(not(Py_LIMITED_API))]
pub const fn fastcall_cfunction_with_keywords(
name: &'static str,
cfunction: PyCFunctionFastWithKeywords,
@ -135,7 +135,7 @@ 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)))]
#[cfg(not(Py_LIMITED_API))]
PyMethodType::PyCFunctionFastWithKeywords(meth) => unsafe {
std::mem::transmute(meth.0)
},

View file

@ -8,7 +8,6 @@ mod macros;
pub mod basic;
#[cfg(not(Py_LIMITED_API))]
pub mod buffer;
pub mod context;
pub mod descr;
pub mod gc;
#[doc(hidden)]
@ -24,7 +23,6 @@ pub mod sequence;
pub use self::basic::PyObjectProtocol;
#[cfg(not(Py_LIMITED_API))]
pub use self::buffer::PyBufferProtocol;
pub use self::context::PyContextProtocol;
pub use self::descr::PyDescrProtocol;
pub use self::gc::{PyGCProtocol, PyTraverseError, PyVisit};
pub use self::iter::PyIterProtocol;

View file

@ -283,16 +283,6 @@ pub trait PyNumberProtocol<'p>: PyClass {
{
unimplemented!()
}
#[deprecated(
since = "0.14.0",
note = "prefer implementing `__complex__` in `#[pymethods]` instead of in a protocol"
)]
fn __complex__(&'p self) -> Self::Result
where
Self: PyNumberComplexProtocol<'p>,
{
unimplemented!()
}
fn __int__(&'p self) -> Self::Result
where
Self: PyNumberIntProtocol<'p>,
@ -311,16 +301,6 @@ pub trait PyNumberProtocol<'p>: PyClass {
{
unimplemented!()
}
#[deprecated(
since = "0.14.0",
note = "prefer implementing `__round__` in `#[pymethods]` instead of in a protocol"
)]
fn __round__(&'p self, ndigits: Option<Self::NDigits>) -> Self::Result
where
Self: PyNumberRoundProtocol<'p>,
{
unimplemented!()
}
}
pub trait PyNumberAddProtocol<'p>: PyNumberProtocol<'p> {
@ -569,10 +549,6 @@ pub trait PyNumberInvertProtocol<'p>: PyNumberProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>;
}
pub trait PyNumberComplexProtocol<'p>: PyNumberProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>;
}
pub trait PyNumberIntProtocol<'p>: PyNumberProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>;
}
@ -581,11 +557,6 @@ pub trait PyNumberFloatProtocol<'p>: PyNumberProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>;
}
pub trait PyNumberRoundProtocol<'p>: PyNumberProtocol<'p> {
type NDigits: FromPyObject<'p>;
type Result: IntoPyCallbackOutput<PyObject>;
}
pub trait PyNumberIndexProtocol<'p>: PyNumberProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>;
}

View file

@ -37,33 +37,6 @@ pub trait PyAsyncProtocol<'p>: PyClass {
{
unimplemented!()
}
#[deprecated(
since = "0.14.0",
note = "prefer implementing `__aenter__` in `#[pymethods]` instead of in a protocol"
)]
fn __aenter__(&'p mut self) -> Self::Result
where
Self: PyAsyncAenterProtocol<'p>,
{
unimplemented!()
}
#[deprecated(
since = "0.14.0",
note = "prefer implementing `__aexit__` in `#[pymethods]` instead of in a protocol"
)]
fn __aexit__(
&'p mut self,
exc_type: Option<Self::ExcType>,
exc_value: Option<Self::ExcValue>,
traceback: Option<Self::Traceback>,
) -> Self::Result
where
Self: PyAsyncAexitProtocol<'p>,
{
unimplemented!()
}
}
pub trait PyAsyncAwaitProtocol<'p>: PyAsyncProtocol<'p> {
@ -81,17 +54,6 @@ pub trait PyAsyncAnextProtocol<'p>: PyAsyncProtocol<'p> {
type Result: IntoPyCallbackOutput<PyIterANextOutput>;
}
pub trait PyAsyncAenterProtocol<'p>: PyAsyncProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>;
}
pub trait PyAsyncAexitProtocol<'p>: PyAsyncProtocol<'p> {
type ExcType: crate::FromPyObject<'p>;
type ExcValue: crate::FromPyObject<'p>;
type Traceback: crate::FromPyObject<'p>;
type Result: IntoPyCallbackOutput<PyObject>;
}
py_unarys_func!(await_, PyAsyncAwaitProtocol, Self::__await__);
py_unarys_func!(aiter, PyAsyncAiterProtocol, Self::__aiter__);
py_unarys_func!(anext, PyAsyncAnextProtocol, Self::__anext__);

View file

@ -463,6 +463,10 @@ impl IntoPy<Py<PyTuple>> for () {
}
/// Raw level conversion between `*mut ffi::PyObject` and PyO3 types.
///
/// # Safety
///
/// See safety notes on individual functions.
pub unsafe trait FromPyPointer<'p>: Sized {
/// Convert from an arbitrary `PyObject`.
///

View file

@ -117,8 +117,8 @@ impl From<anyhow::Error> for PyErr {
#[cfg(test)]
mod test_anyhow {
use pyo3::prelude::*;
use pyo3::types::IntoPyDict;
use crate::prelude::*;
use crate::types::IntoPyDict;
use anyhow::{anyhow, bail, Context, Result};
@ -144,7 +144,7 @@ mod test_anyhow {
Python::with_gil(|py| {
let locals = [("err", pyerr)].into_py_dict(py);
let pyerr = py.run("raise err", None, Some(locals)).unwrap_err();
assert_eq!(pyerr.pvalue(py).to_string(), expected_contents);
assert_eq!(pyerr.value(py).to_string(), expected_contents);
})
}
@ -161,7 +161,7 @@ mod test_anyhow {
Python::with_gil(|py| {
let locals = [("err", pyerr)].into_py_dict(py);
let pyerr = py.run("raise err", None, Some(locals)).unwrap_err();
assert_eq!(pyerr.pvalue(py).to_string(), expected_contents);
assert_eq!(pyerr.value(py).to_string(), expected_contents);
})
}
}

View file

@ -118,8 +118,8 @@ impl From<eyre::Report> for PyErr {
#[cfg(test)]
mod tests {
use pyo3::prelude::*;
use pyo3::types::IntoPyDict;
use crate::prelude::*;
use crate::types::IntoPyDict;
use eyre::{bail, Result, WrapErr};
@ -145,7 +145,7 @@ mod tests {
Python::with_gil(|py| {
let locals = [("err", pyerr)].into_py_dict(py);
let pyerr = py.run("raise err", None, Some(locals)).unwrap_err();
assert_eq!(pyerr.pvalue(py).to_string(), expected_contents);
assert_eq!(pyerr.value(py).to_string(), expected_contents);
})
}
}

View file

@ -20,7 +20,7 @@ impl FromPyObject<'_> for PathBuf {
let py = ob.py();
let pathlib = py.import("pathlib")?;
let pathlib_path: &PyType = pathlib.getattr("Path")?.downcast()?;
if pathlib_path.is_instance(ob)? {
if ob.is_instance(pathlib_path)? {
let path_str = ob.call_method0("__str__")?;
OsString::extract(path_str)?
} else {

View file

@ -273,9 +273,9 @@ impl FunctionDescription {
/// Add the argument name to the error message of an error which occurred during argument extraction
pub fn argument_extraction_error(py: Python, arg_name: &str, error: PyErr) -> PyErr {
if error.ptype(py) == py.get_type::<PyTypeError>() {
if error.is_instance_of::<PyTypeError>(py) {
let reason = error
.instance(py)
.value(py)
.str()
.unwrap_or_else(|_| PyString::new(py, ""));
PyTypeError::new_err(format!("argument '{}': {}", arg_name, reason))

View file

@ -2,7 +2,7 @@ use crate::{
exceptions::{PyBaseException, PyTypeError},
ffi,
type_object::PyTypeObject,
types::PyType,
types::{PyTraceback, PyType},
AsPyPointer, IntoPy, IntoPyPointer, Py, PyObject, Python,
};
@ -10,7 +10,7 @@ use crate::{
pub(crate) struct PyErrStateNormalized {
pub ptype: Py<PyType>,
pub pvalue: Py<PyBaseException>,
pub ptraceback: Option<PyObject>,
pub ptraceback: Option<Py<PyTraceback>>,
}
pub(crate) enum PyErrState {

View file

@ -2,12 +2,14 @@
use crate::panic::PanicException;
use crate::type_object::PyTypeObject;
use crate::types::PyType;
use crate::types::{PyTraceback, PyType};
use crate::{
exceptions::{self, PyBaseException},
ffi,
};
use crate::{AsPyPointer, IntoPy, Py, PyAny, PyObject, Python, ToBorrowedObject, ToPyObject};
use crate::{
AsPyPointer, IntoPy, IntoPyPointer, Py, PyAny, PyObject, Python, ToBorrowedObject, ToPyObject,
};
use std::borrow::Cow;
use std::cell::UnsafeCell;
use std::ffi::CString;
@ -19,7 +21,14 @@ mod impls;
pub use err_state::PyErrArguments;
use err_state::{boxed_args, PyErrState, PyErrStateNormalized};
/// Represents a Python exception that was raised.
/// Represents a Python exception.
///
/// Python exceptions can be raised in a "lazy" fashion, where the full Python object for the
/// exception is not created until needed. The process of creating the full object is known
/// as "normalization". An exception which has not yet been created is known as "unnormalized".
///
/// This struct builds upon that design, supporting all lazily-created Python exceptions and also
/// supporting exceptions lazily-created from Rust.
pub struct PyErr {
// Safety: can only hand out references when in the "normalized" state. Will never change
// after normalization.
@ -54,15 +63,21 @@ impl<'a> PyDowncastError<'a> {
impl PyErr {
/// Creates a new PyErr of type `T`.
///
/// `value` can be:
/// * a tuple: the exception instance will be created using Python `T(*tuple)`
/// * any other value: the exception instance will be created using Python `T(value)`
/// `args` can be:
/// * a tuple: the exception instance will be created using the equivalent to the Python
/// expression `T(*tuple)`
/// * any other value: the exception instance will be created using the equivalent to the Python
/// expression `T(value)`
///
/// Note: if `value` is not `Send` or `Sync`, consider using `PyErr::from_instance` instead.
/// This error will be stored in an unnormalized state. This avoids the need for the Python GIL
/// to be held, but requires `args` to be `Send` and `Sync`. If `args` is not `Send` or `Sync`,
/// consider using [`PyErr::from_value`] instead.
///
/// Panics if `T` is not a Python class derived from `BaseException`.
/// If an error occurs during normalization (for example if `T` is not a Python type which
/// extends from `BaseException`), then a different error may be produced during normalization.
///
/// # Example
///
/// Example:
/// ```ignore
/// return Err(PyErr::new::<exceptions::PyTypeError, _>("Error message"));
/// ```
@ -71,6 +86,7 @@ impl PyErr {
/// ```ignore
/// return Err(exceptions::PyTypeError::new_err("Error message"));
/// ```
#[inline]
pub fn new<T, A>(args: A) -> PyErr
where
T: PyTypeObject,
@ -82,11 +98,17 @@ impl PyErr {
})
}
/// Constructs a new error, with the usual lazy initialization of Python exceptions.
/// Constructs a new PyErr from the given Python type and arguments.
///
/// `exc` is the exception type; usually one of the standard exceptions
/// `ty` is the exception type; usually one of the standard exceptions
/// like `exceptions::PyRuntimeError`.
/// `args` is the a tuple of arguments to pass to the exception constructor.
///
/// `args` is either a tuple or a single value, with the same meaning as in [`PyErr::new`].
///
/// If an error occurs during normalization (for example if `T` is not a Python type which
/// extends from `BaseException`), then a different error may be produced during normalization.
///
/// This error will be stored in an unnormalized state.
pub fn from_type<A>(ty: &PyType, args: A) -> PyErr
where
A: PyErrArguments + Send + Sync + 'static,
@ -103,32 +125,34 @@ impl PyErr {
/// Creates a new PyErr.
///
/// `obj` must be an Python exception instance, the PyErr will use that instance.
/// If `obj` is a Python exception type object, the PyErr will (lazily) create a new
/// instance of that type.
/// Otherwise, a `TypeError` is created instead.
/// If `obj` is a Python exception object, the PyErr will contain that object. The error will be
/// in a normalized state.
///
/// If `obj` is a Python exception type object, this is equivalent to `PyErr::from_type(obj, ())`.
///
/// Otherwise, a `TypeError` is created.
///
/// # Examples
/// ```rust
/// use pyo3::{exceptions::PyTypeError, types::PyType, IntoPy, PyErr, Python};
/// Python::with_gil(|py| {
/// // Case #1: Exception instance
/// let err = PyErr::from_instance(PyTypeError::new_err("some type error").instance(py));
/// // Case #1: Exception object
/// let err = PyErr::from_value(PyTypeError::new_err("some type error").value(py));
/// assert_eq!(err.to_string(), "TypeError: some type error");
///
/// // Case #2: Exception type
/// let err = PyErr::from_instance(PyType::new::<PyTypeError>(py));
/// let err = PyErr::from_value(PyType::new::<PyTypeError>(py));
/// assert_eq!(err.to_string(), "TypeError: ");
///
/// // Case #3: Invalid exception value
/// let err = PyErr::from_instance("foo".into_py(py).as_ref(py));
/// let err = PyErr::from_value("foo".into_py(py).as_ref(py));
/// assert_eq!(
/// err.to_string(),
/// "TypeError: exceptions must derive from BaseException"
/// );
/// });
/// ```
pub fn from_instance(obj: &PyAny) -> PyErr {
pub fn from_value(obj: &PyAny) -> PyErr {
let ptr = obj.as_ptr();
let state = if unsafe { ffi::PyExceptionInstance_Check(ptr) } != 0 {
@ -150,7 +174,7 @@ impl PyErr {
PyErr::from_state(state)
}
/// Get the type of this exception object.
/// Returns the type of this exception.
///
/// The object will be normalized first if needed.
///
@ -160,14 +184,14 @@ impl PyErr {
///
/// Python::with_gil(|py| {
/// let err: PyErr = PyTypeError::new_err(("some type error",));
/// assert_eq!(err.ptype(py), PyType::new::<PyTypeError>(py));
/// assert_eq!(err.get_type(py), PyType::new::<PyTypeError>(py));
/// });
/// ```
pub fn ptype<'py>(&'py self, py: Python<'py>) -> &'py PyType {
pub fn get_type<'py>(&'py self, py: Python<'py>) -> &'py PyType {
self.normalized(py).ptype.as_ref(py)
}
/// Get the value of this exception object.
/// Returns the value of this exception.
///
/// The object will be normalized first if needed.
///
@ -178,15 +202,23 @@ impl PyErr {
///
/// Python::with_gil(|py| {
/// let err: PyErr = PyTypeError::new_err(("some type error",));
/// assert!(err.is_instance::<PyTypeError>(py));
/// assert_eq!(err.pvalue(py).to_string(), "some type error");
/// assert!(err.is_instance_of::<PyTypeError>(py));
/// assert_eq!(err.value(py).to_string(), "some type error");
/// });
/// ```
pub fn pvalue<'py>(&'py self, py: Python<'py>) -> &'py PyBaseException {
pub fn value<'py>(&'py self, py: Python<'py>) -> &'py PyBaseException {
self.normalized(py).pvalue.as_ref(py)
}
/// Get the value of this exception object.
/// Consumes self to take ownership of the exception value contained in this error.
pub fn into_value(self, py: Python) -> Py<PyBaseException> {
// NB technically this causes one reference count increase and decrease in quick succession
// on pvalue, but it's probably not worth optimizing this right now for the additional code
// complexity.
self.normalized(py).pvalue.clone_ref(py)
}
/// Returns the traceback of this exception object.
///
/// The object will be normalized first if needed.
///
@ -196,10 +228,10 @@ impl PyErr {
///
/// Python::with_gil(|py| {
/// let err = PyTypeError::new_err(("some type error",));
/// assert_eq!(err.ptraceback(py), None);
/// assert_eq!(err.traceback(py), None);
/// });
/// ```
pub fn ptraceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyAny> {
pub fn traceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyTraceback> {
self.normalized(py)
.ptraceback
.as_ref()
@ -219,8 +251,8 @@ impl PyErr {
/// callback) then this function will resume the panic.
///
/// Use this function when it is not known if an error should be present. If the error is
/// expected to have been set, for example from [PyErr::occurred] or by an error return value
/// from a C FFI function, use [PyErr::fetch].
/// expected to have been set, for example from [`PyErr::occurred`] or by an error return value
/// from a C FFI function, use [`PyErr::fetch`].
pub fn take(py: Python) -> Option<PyErr> {
let (ptype, pvalue, ptraceback) = unsafe {
let mut ptype: *mut ffi::PyObject = std::ptr::null_mut();
@ -265,7 +297,6 @@ impl PyErr {
eprintln!("Python stack trace below:");
unsafe {
use crate::conversion::IntoPyPointer;
ffi::PyErr_Restore(ptype.into_ptr(), pvalue.into_ptr(), ptraceback.into_ptr());
ffi::PyErr_PrintEx(0);
}
@ -380,30 +411,23 @@ impl PyErr {
T: ToBorrowedObject,
{
exc.with_borrowed_ptr(py, |exc| unsafe {
ffi::PyErr_GivenExceptionMatches(self.ptype_ptr(py), exc) != 0
ffi::PyErr_GivenExceptionMatches(self.type_ptr(py), exc) != 0
})
}
/// Returns true if the current exception is instance of `T`.
pub fn is_instance<T>(&self, py: Python) -> bool
#[inline]
pub fn is_instance(&self, py: Python, typ: &PyType) -> bool {
unsafe { ffi::PyErr_GivenExceptionMatches(self.type_ptr(py), typ.as_ptr()) != 0 }
}
/// Returns true if the current exception is instance of `T`.
#[inline]
pub fn is_instance_of<T>(&self, py: Python) -> bool
where
T: PyTypeObject,
{
unsafe {
ffi::PyErr_GivenExceptionMatches(self.ptype_ptr(py), T::type_object(py).as_ptr()) != 0
}
}
/// Retrieves the exception instance for this error.
pub fn instance<'py>(&'py self, py: Python<'py>) -> &'py PyBaseException {
self.normalized(py).pvalue.as_ref(py)
}
/// Consumes self to take ownership of the exception instance for this error.
pub fn into_instance(self, py: Python) -> Py<PyBaseException> {
let out = self.normalized(py).pvalue.as_ref(py).into();
std::mem::forget(self);
out
self.is_instance(py, T::type_object(py))
}
/// Writes the error back to the Python interpreter's global state.
@ -442,11 +466,12 @@ impl PyErr {
/// Python::with_gil(|py| {
/// let err: PyErr = PyTypeError::new_err(("some type error",));
/// let err_clone = err.clone_ref(py);
/// assert_eq!(err.ptype(py), err_clone.ptype(py));
/// assert_eq!(err.pvalue(py), err_clone.pvalue(py));
/// assert_eq!(err.ptraceback(py), err_clone.ptraceback(py));
/// assert_eq!(err.get_type(py), err_clone.get_type(py));
/// assert_eq!(err.value(py), err_clone.value(py));
/// assert_eq!(err.traceback(py), err_clone.traceback(py));
/// });
/// ```
#[inline]
pub fn clone_ref(&self, py: Python) -> PyErr {
PyErr::from_state(PyErrState::Normalized(self.normalized(py).clone()))
}
@ -454,25 +479,23 @@ impl PyErr {
/// Return the cause (either an exception instance, or None, set by `raise ... from ...`)
/// associated with the exception, as accessible from Python through `__cause__`.
pub fn cause(&self, py: Python) -> Option<PyErr> {
let ptr = unsafe { ffi::PyException_GetCause(self.pvalue(py).as_ptr()) };
let ptr = unsafe { ffi::PyException_GetCause(self.value(py).as_ptr()) };
let obj = unsafe { py.from_owned_ptr_or_opt::<PyAny>(ptr) };
obj.map(|x| Self::from_instance(x))
obj.map(Self::from_value)
}
/// Set the cause associated with the exception, pass `None` to clear it.
pub fn set_cause(&self, py: Python, cause: Option<Self>) {
if let Some(cause) = cause {
let cause = cause.into_instance(py);
unsafe {
ffi::PyException_SetCause(self.pvalue(py).as_ptr(), cause.as_ptr());
}
} else {
unsafe {
ffi::PyException_SetCause(self.pvalue(py).as_ptr(), std::ptr::null_mut());
}
unsafe {
// PyException_SetCause _steals_ a reference to cause, so must use .into_ptr()
ffi::PyException_SetCause(
self.value(py).as_ptr(),
cause.map_or(std::ptr::null_mut(), |err| err.into_value(py).into_ptr()),
);
}
}
#[inline]
fn from_state(state: PyErrState) -> PyErr {
PyErr {
state: UnsafeCell::new(Some(state)),
@ -480,7 +503,7 @@ impl PyErr {
}
/// Returns borrowed reference to this Err's type
fn ptype_ptr(&self, py: Python) -> *mut ffi::PyObject {
fn type_ptr(&self, py: Python) -> *mut ffi::PyObject {
match unsafe { &*self.state.get() } {
// In lazy type case, normalize before returning ptype in case the type is not a valid
// exception type.
@ -492,17 +515,26 @@ impl PyErr {
}
}
#[inline]
fn normalized(&self, py: Python) -> &PyErrStateNormalized {
if let Some(PyErrState::Normalized(n)) = unsafe {
// Safety: self.state will never be written again once normalized.
&*self.state.get()
} {
return n;
}
self.make_normalized(py)
}
#[cold]
fn make_normalized(&self, py: Python) -> &PyErrStateNormalized {
// This process is safe because:
// - Access is guaranteed not to be concurrent thanks to `Python` GIL token
// - Write happens only once, and then never will change again.
// - State is set to None during the normalization process, so that a second
// concurrent normalization attempt will panic before changing anything.
if let Some(PyErrState::Normalized(n)) = unsafe { &*self.state.get() } {
return n;
}
let state = unsafe {
(*self.state.get())
.take()
@ -516,7 +548,7 @@ impl PyErr {
*self_state = Some(PyErrState::Normalized(PyErrStateNormalized {
ptype: Py::from_owned_ptr_or_opt(py, ptype).expect("Exception type missing"),
pvalue: Py::from_owned_ptr_or_opt(py, pvalue).expect("Exception value missing"),
ptraceback: PyObject::from_owned_ptr_or_opt(py, ptraceback),
ptraceback: Py::from_owned_ptr_or_opt(py, ptraceback),
}));
match self_state {
@ -525,15 +557,66 @@ impl PyErr {
}
}
}
/// Deprecated name for [`PyErr::get_type`].
#[deprecated(
since = "0.16.0",
note = "Use err.get_type(py) instead of err.ptype(py)"
)]
pub fn ptype<'py>(&'py self, py: Python<'py>) -> &'py PyType {
self.get_type(py)
}
/// Deprecated name for [`PyErr::value`].
#[deprecated(since = "0.16.0", note = "Use err.value(py) instead of err.pvalue(py)")]
pub fn pvalue<'py>(&'py self, py: Python<'py>) -> &'py PyBaseException {
self.value(py)
}
/// Deprecated name for [`PyErr::traceback`].
#[deprecated(
since = "0.16.0",
note = "Use err.traceback(py) instead of err.ptraceback(py)"
)]
pub fn ptraceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyTraceback> {
self.traceback(py)
}
/// Deprecated name for [`PyErr::value`].
#[deprecated(
since = "0.16.0",
note = "Use err.value(py) instead of err.instance(py)"
)]
pub fn instance<'py>(&'py self, py: Python<'py>) -> &'py PyBaseException {
self.value(py)
}
/// Deprecated name for [`PyErr::from_value`].
#[deprecated(
since = "0.16.0",
note = "Use err.from_value(py, obj) instead of err.from_instance(py, obj)"
)]
pub fn from_instance(value: &PyAny) -> PyErr {
PyErr::from_value(value)
}
/// Deprecated name for [`PyErr::into_value`].
#[deprecated(
since = "0.16.0",
note = "Use err.into_value(py) instead of err.into_instance(py)"
)]
pub fn into_instance(self, py: Python) -> Py<PyBaseException> {
self.into_value(py)
}
}
impl std::fmt::Debug for PyErr {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
Python::with_gil(|py| {
f.debug_struct("PyErr")
.field("type", self.ptype(py))
.field("value", self.pvalue(py))
.field("traceback", &self.ptraceback(py))
.field("type", self.get_type(py))
.field("value", self.value(py))
.field("traceback", &self.traceback(py))
.finish()
})
}
@ -542,10 +625,10 @@ impl std::fmt::Debug for PyErr {
impl std::fmt::Display for PyErr {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
Python::with_gil(|py| {
let instance = self.instance(py);
let type_name = instance.get_type().name().map_err(|_| std::fmt::Error)?;
let value = self.value(py);
let type_name = value.get_type().name().map_err(|_| std::fmt::Error)?;
write!(f, "{}", type_name)?;
if let Ok(s) = instance.str() {
if let Ok(s) = value.str() {
write!(f, ": {}", &s.to_string_lossy())
} else {
write!(f, ": <exception str() failed>")
@ -558,7 +641,7 @@ impl std::error::Error for PyErr {}
impl IntoPy<PyObject> for PyErr {
fn into_py(self, py: Python) -> PyObject {
self.into_instance(py).into()
self.into_value(py).into()
}
}
@ -631,11 +714,11 @@ mod tests {
fn set_valueerror() {
Python::with_gil(|py| {
let err: PyErr = exceptions::PyValueError::new_err("some exception message");
assert!(err.is_instance::<exceptions::PyValueError>(py));
assert!(err.is_instance_of::<exceptions::PyValueError>(py));
err.restore(py);
assert!(PyErr::occurred(py));
let err = PyErr::fetch(py);
assert!(err.is_instance::<exceptions::PyValueError>(py));
assert!(err.is_instance_of::<exceptions::PyValueError>(py));
assert_eq!(err.to_string(), "ValueError: some exception message");
})
}
@ -644,10 +727,10 @@ mod tests {
fn invalid_error_type() {
Python::with_gil(|py| {
let err: PyErr = PyErr::new::<crate::types::PyString, _>(());
assert!(err.is_instance::<exceptions::PyTypeError>(py));
assert!(err.is_instance_of::<exceptions::PyTypeError>(py));
err.restore(py);
let err = PyErr::fetch(py);
assert!(err.is_instance::<exceptions::PyTypeError>(py));
assert!(err.is_instance_of::<exceptions::PyTypeError>(py));
assert_eq!(
err.to_string(),
"TypeError: exceptions must derive from BaseException"
@ -702,12 +785,7 @@ mod tests {
let mut fields = debug_str["PyErr { ".len()..debug_str.len() - 2].split(", ");
assert_eq!(fields.next().unwrap(), "type: <class 'Exception'>");
if py.version_info() >= (3, 7) {
assert_eq!(fields.next().unwrap(), "value: Exception('banana')");
} else {
// Python 3.6 and below formats the repr differently
assert_eq!(fields.next().unwrap(), ("value: Exception('banana',)"));
}
assert_eq!(fields.next().unwrap(), "value: Exception('banana')");
let traceback = fields.next().unwrap();
assert!(traceback.starts_with("traceback: Some(<traceback object at 0x"));
@ -770,4 +848,22 @@ mod tests {
assert_eq!(cause.to_string(), "ValueError: orange");
});
}
#[allow(deprecated)]
#[test]
fn deprecations() {
let err = exceptions::PyValueError::new_err("an error");
Python::with_gil(|py| {
assert_eq!(err.ptype(py), err.get_type(py));
assert_eq!(err.pvalue(py), err.value(py));
assert_eq!(err.instance(py), err.value(py));
assert_eq!(err.ptraceback(py), err.traceback(py));
assert_eq!(
err.clone_ref(py).into_instance(py).as_ref(py),
err.value(py)
);
assert_eq!(PyErr::from_instance(err.value(py)).value(py), err.value(py));
});
}
}

View file

@ -21,7 +21,7 @@ macro_rules! impl_exception_boilerplate {
impl ::std::convert::From<&$name> for $crate::PyErr {
#[inline]
fn from(err: &$name) -> $crate::PyErr {
$crate::PyErr::from_instance(err)
$crate::PyErr::from_value(err)
}
}
@ -312,7 +312,7 @@ fn always_throws() -> PyResult<()> {
# Python::with_gil(|py| {
# let fun = pyo3::wrap_pyfunction!(always_throws, py).unwrap();
# let err = fun.call0().expect_err(\"called a function that should always return an error but the return value was Ok\");
# assert!(err.is_instance::<Py", $name, ">(py))
# assert!(err.is_instance_of::<Py", $name, ">(py))
# });
```
@ -337,7 +337,7 @@ Python::with_gil(|py| {
let error_type = match result {
Ok(_) => \"Not an error\",
Err(error) if error.is_instance::<Py", $name, ">(py) => \"" , $name, "\",
Err(error) if error.is_instance_of::<Py", $name, ">(py) => \"" , $name, "\",
Err(_) => \"Some other error\",
};
@ -656,15 +656,15 @@ macro_rules! test_exception {
.unwrap_or($exc_ty::new_err("a test exception"))
};
assert!(err.is_instance::<$exc_ty>(py));
assert!(err.is_instance_of::<$exc_ty>(py));
let value: &$exc_ty = err.instance(py).downcast().unwrap();
let value: &$exc_ty = err.value(py).downcast().unwrap();
assert!(value.source().is_none());
err.set_cause(py, Some($crate::exceptions::PyValueError::new_err("a cause")));
assert!(value.source().is_some());
assert!($crate::PyErr::from(value).is_instance::<$exc_ty>(py));
assert!($crate::PyErr::from(value).is_instance_of::<$exc_ty>(py));
})
}
};
@ -859,7 +859,7 @@ mod tests {
let exc = py
.run("raise Exception('banana')", None, None)
.expect_err("raising should have given us an error")
.into_instance(py)
.into_value(py)
.into_ref(py);
assert_eq!(
format!("{:?}", exc),
@ -874,7 +874,7 @@ mod tests {
let exc = py
.run("raise Exception('banana')", None, None)
.expect_err("raising should have given us an error")
.into_instance(py)
.into_value(py)
.into_ref(py);
assert_eq!(
exc.to_string(),
@ -895,22 +895,14 @@ mod tests {
None,
)
.expect_err("raising should have given us an error")
.into_instance(py)
.into_value(py)
.into_ref(py);
if py.version_info() >= (3, 7) {
assert_eq!(format!("{:?}", exc), "Exception('banana')");
} else {
assert_eq!(format!("{:?}", exc), "Exception('banana',)");
}
assert_eq!(format!("{:?}", exc), "Exception('banana')");
let source = exc.source().expect("cause should exist");
if py.version_info() >= (3, 7) {
assert_eq!(format!("{:?}", source), "TypeError('peach')");
} else {
assert_eq!(format!("{:?}", source), "TypeError('peach',)");
}
assert_eq!(format!("{:?}", source), "TypeError('peach')");
let source_source = source.source();
assert!(source_source.is_none(), "source_source should be None");
@ -973,7 +965,7 @@ mod tests {
test_exception!(PyUnicodeDecodeError, |py| {
let invalid_utf8 = b"fo\xd8o";
let err = std::str::from_utf8(invalid_utf8).expect_err("should be invalid utf8");
PyErr::from_instance(PyUnicodeDecodeError::new_utf8(py, invalid_utf8, err).unwrap())
PyErr::from_value(PyUnicodeDecodeError::new_utf8(py, invalid_utf8, err).unwrap())
});
test_exception!(PyUnicodeEncodeError, |py: Python<'_>| {
py.eval("chr(40960).encode('ascii')", None, None)

View file

@ -91,7 +91,7 @@ extern "C" {
pub fn PyObject_GetIter(arg1: *mut PyObject) -> *mut PyObject;
}
// Defined as this macro in Python 3.6, 3.7 limited API, but relies on
// Defined as this macro in Python limited API, but relies on
// non-limited PyTypeObject. Don't expose this since it cannot be used.
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
#[inline]
@ -156,7 +156,7 @@ extern "C" {
pub fn PyNumber_Or(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject;
}
// Defined as this macro in Python 3.6, 3.7 limited API, but relies on
// Defined as this macro in Python limited API, but relies on
// non-limited PyTypeObject. Don't expose this since it cannot be used.
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
#[inline]

View file

@ -52,7 +52,6 @@ extern "C" {
fn _Py_CheckRecursiveCall(_where: *mut c_char) -> c_int;
}
// TODO
// skipped Py_EnterRecursiveCall
// skipped Py_LeaveRecursiveCall
@ -68,7 +67,6 @@ extern "C" {
pub fn PyEval_RestoreThread(arg1: *mut PyThreadState);
}
#[cfg(py_sys_config = "WITH_THREAD")]
extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyEval_ThreadsInitialized")]
pub fn PyEval_ThreadsInitialized() -> c_int;

View file

@ -6,33 +6,19 @@ use std::os::raw::{c_char, c_int, c_uchar};
extern "C" {
pub fn _PyImport_IsInitialized(state: *mut PyInterpreterState) -> c_int;
// skipped _PyImport_GetModuleId
#[cfg(Py_3_7)]
pub fn _PyImport_SetModule(name: *mut PyObject, module: *mut PyObject) -> c_int;
#[cfg(Py_3_7)]
pub fn _PyImport_SetModuleString(name: *const c_char, module: *mut PyObject) -> c_int;
pub fn _PyImport_AcquireLock();
pub fn _PyImport_ReleaseLock() -> c_int;
#[cfg(not(Py_3_7))]
pub fn _PyImport_FindBuiltin(name: *const c_char) -> *mut PyObject;
#[cfg(all(Py_3_7, not(Py_3_9)))]
#[cfg(not(Py_3_9))]
pub fn _PyImport_FindBuiltin(name: *const c_char, modules: *mut PyObject) -> *mut PyObject;
#[cfg(not(Py_3_11))]
pub fn _PyImport_FindExtensionObject(a: *mut PyObject, b: *mut PyObject) -> *mut PyObject;
#[cfg(not(Py_3_7))]
pub fn _PyImport_FixupBuiltin(module: *mut PyObject, name: *const c_char) -> c_int;
#[cfg(Py_3_7)]
pub fn _PyImport_FixupBuiltin(
module: *mut PyObject,
name: *const c_char,
modules: *mut PyObject,
) -> c_int;
#[cfg(not(Py_3_7))]
pub fn _PyImport_FixupExtensionObject(
a: *mut PyObject,
b: *mut PyObject,
c: *mut PyObject,
) -> c_int;
#[cfg(Py_3_7)]
pub fn _PyImport_FixupExtensionObject(
a: *mut PyObject,
b: *mut PyObject,

View file

@ -323,14 +323,9 @@ extern "C" {
extern "C" {
// skipped _PyUnicode_AsStringAndSize
#[cfg(Py_3_7)]
#[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8")]
pub fn PyUnicode_AsUTF8(unicode: *mut PyObject) -> *const c_char;
#[cfg(not(Py_3_7))]
#[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8")]
pub fn PyUnicode_AsUTF8(unicode: *mut PyObject) -> *mut c_char;
// skipped _PyUnicode_AsString
pub fn PyUnicode_Encode(

View file

@ -361,7 +361,7 @@ pub struct PyDateTime_CAPI {
pub TimeType: *mut PyTypeObject,
pub DeltaType: *mut PyTypeObject,
pub TZInfoType: *mut PyTypeObject,
#[cfg(all(Py_3_7, any(not(PyPy), Py_3_8)))]
#[cfg(not(all(PyPy, not(Py_3_8))))]
pub TimeZone_UTC: *mut PyObject,
pub Date_FromDate: unsafe extern "C" fn(
year: c_int,
@ -395,7 +395,7 @@ pub struct PyDateTime_CAPI {
normalize: c_int,
cls: *mut PyTypeObject,
) -> *mut PyObject,
#[cfg(all(Py_3_7, any(not(PyPy), Py_3_8)))]
#[cfg(not(all(PyPy, not(Py_3_8))))]
pub TimeZone_FromTimeZone:
unsafe extern "C" fn(offset: *mut PyObject, name: *mut PyObject) -> *mut PyObject,
@ -451,7 +451,7 @@ pub static PyDateTimeAPI: _PyDateTimeAPI_impl = _PyDateTimeAPI_impl {
///
/// The type obtained by dereferencing this object is `&'static PyObject`. This may change in the
/// future to be a more specific type representing that this is a `datetime.timezone` object.
#[cfg(all(Py_3_7, any(not(PyPy), Py_3_8)))]
#[cfg(not(all(PyPy, not(Py_3_8))))]
pub static PyDateTime_TimeZone_UTC: _PyDateTime_TimeZone_UTC_impl = _PyDateTime_TimeZone_UTC_impl {
inner: &PyDateTimeAPI,
};
@ -609,12 +609,12 @@ impl Deref for _PyDateTimeAPI_impl {
}
#[doc(hidden)]
#[cfg(all(Py_3_7, any(not(PyPy), Py_3_8)))]
#[cfg(not(all(PyPy, not(Py_3_8))))]
pub struct _PyDateTime_TimeZone_UTC_impl {
inner: &'static _PyDateTimeAPI_impl,
}
#[cfg(all(Py_3_7, any(not(PyPy), Py_3_8)))]
#[cfg(not(all(PyPy, not(Py_3_8))))]
impl Deref for _PyDateTime_TimeZone_UTC_impl {
type Target = crate::PyObject;
@ -630,7 +630,7 @@ impl Deref for _PyDateTime_TimeZone_UTC_impl {
#[cfg(test)]
mod tests {
use super::*;
use crate::{py_run, AsPyPointer, IntoPy, Py, PyAny, Python};
use crate::{types::PyDict, AsPyPointer, IntoPy, Py, PyAny, Python};
#[test]
fn test_datetime_fromtimestamp() {
@ -638,11 +638,14 @@ mod tests {
let args: Py<PyAny> = (100,).into_py(py);
unsafe { PyDateTime_IMPORT() };
let dt: &PyAny = unsafe { py.from_owned_ptr(PyDateTime_FromTimestamp(args.as_ptr())) };
py_run!(
py,
dt,
"import datetime; assert dt == datetime.datetime.fromtimestamp(100)"
);
let locals = PyDict::new(py);
locals.set_item("dt", dt).unwrap();
py.run(
"import datetime; assert dt == datetime.datetime.fromtimestamp(100)",
None,
Some(locals),
)
.unwrap();
})
}
@ -652,24 +655,30 @@ mod tests {
let args: Py<PyAny> = (100,).into_py(py);
unsafe { PyDateTime_IMPORT() };
let dt: &PyAny = unsafe { py.from_owned_ptr(PyDate_FromTimestamp(args.as_ptr())) };
py_run!(
py,
dt,
"import datetime; assert dt == datetime.date.fromtimestamp(100)"
);
let locals = PyDict::new(py);
locals.set_item("dt", dt).unwrap();
py.run(
"import datetime; assert dt == datetime.date.fromtimestamp(100)",
None,
Some(locals),
)
.unwrap();
})
}
#[test]
#[cfg(all(Py_3_7, any(not(PyPy), Py_3_8)))]
#[cfg(not(all(PyPy, not(Py_3_8))))]
fn test_utc_timezone() {
Python::with_gil(|py| {
let utc_timezone = PyDateTime_TimeZone_UTC.as_ref(py);
py_run!(
py,
utc_timezone,
"import datetime; assert utc_timezone is datetime.timezone.utc"
);
let locals = PyDict::new(py);
locals.set_item("utc_timezone", utc_timezone).unwrap();
py.run(
"import datetime; assert utc_timezone is datetime.timezone.utc",
None,
Some(locals),
)
.unwrap();
})
}
}

View file

@ -69,10 +69,7 @@ extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyImport_ReloadModule")]
pub fn PyImport_ReloadModule(m: *mut PyObject) -> *mut PyObject;
#[cfg(not(Py_3_9))]
#[deprecated(
since = "0.14.0",
note = "Removed in Python 3.9 as it was \"For internal use only\"."
)]
#[deprecated(note = "Removed in Python 3.9 as it was \"For internal use only\".")]
pub fn PyImport_Cleanup();
pub fn PyImport_ImportFrozenModuleObject(name: *mut PyObject) -> c_int;
pub fn PyImport_ImportFrozenModule(name: *const c_char) -> c_int;

View file

@ -4,19 +4,13 @@ extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyOS_InterruptOccurred")]
pub fn PyOS_InterruptOccurred() -> c_int;
#[cfg(not(Py_3_10))]
#[deprecated(
since = "0.14.0",
note = "Not documented in Python API; see Python 3.10 release notes"
)]
#[deprecated(note = "Not documented in Python API; see Python 3.10 release notes")]
pub fn PyOS_InitInterrupts();
#[cfg(any(not(Py_LIMITED_API), Py_3_7))]
pub fn PyOS_BeforeFork();
#[cfg(any(not(Py_LIMITED_API), Py_3_7))]
pub fn PyOS_AfterFork_Parent();
#[cfg(any(not(Py_LIMITED_API), Py_3_7))]
pub fn PyOS_AfterFork_Child();
#[cfg_attr(Py_3_7, deprecated(note = "use PyOS_AfterFork_Child instead"))]
#[deprecated(note = "use PyOS_AfterFork_Child instead")]
#[cfg_attr(PyPy, link_name = "PyPyOS_AfterFork")]
pub fn PyOS_AfterFork();

View file

@ -31,7 +31,7 @@ pub unsafe fn PyCFunction_Check(op: *mut PyObject) -> c_int {
pub type PyCFunction =
unsafe extern "C" fn(slf: *mut PyObject, args: *mut PyObject) -> *mut PyObject;
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
#[cfg(not(Py_LIMITED_API))]
pub type _PyCFunctionFast = unsafe extern "C" fn(
slf: *mut PyObject,
args: *mut *mut PyObject,
@ -45,7 +45,7 @@ pub type PyCFunctionWithKeywords = unsafe extern "C" fn(
kwds: *mut PyObject,
) -> *mut PyObject;
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
#[cfg(not(Py_LIMITED_API))]
pub type _PyCFunctionFastWithKeywords = unsafe extern "C" fn(
slf: *mut PyObject,
args: *const *mut PyObject,
@ -119,7 +119,7 @@ pub const METH_COEXIST: c_int = 0x0040;
/* METH_FASTCALL indicates the PEP 590 Vectorcall calling format. It may
be specified alone or with METH_KEYWORDS. */
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
#[cfg(not(Py_LIMITED_API))]
pub const METH_FASTCALL: c_int = 0x0080;
// skipped METH_STACKLESS

View file

@ -280,6 +280,7 @@ extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyObject_SetAttr")]
pub fn PyObject_SetAttr(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject)
-> c_int;
#[cfg_attr(PyPy, link_name = "PyPyObject_HasAttr")]
pub fn PyObject_HasAttr(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyObject_SelfIter")]
pub fn PyObject_SelfIter(arg1: *mut PyObject) -> *mut PyObject;

View file

@ -25,7 +25,7 @@ extern "C" {
#[cfg(all(Py_3_8, not(PyPy)))]
pub fn PyInterpreterState_GetDict(arg1: *mut PyInterpreterState) -> *mut PyObject;
#[cfg(all(Py_3_7, not(PyPy)))]
#[cfg(not(PyPy))]
pub fn PyInterpreterState_GetID(arg1: *mut PyInterpreterState) -> i64;
#[cfg(not(PyPy))]

View file

@ -52,20 +52,8 @@ extern "C" {
stop: *mut Py_ssize_t,
step: *mut Py_ssize_t,
) -> c_int;
#[cfg(not(Py_3_7))]
#[cfg_attr(PyPy, link_name = "PyPySlice_GetIndicesEx")]
pub fn PySlice_GetIndicesEx(
r: *mut PyObject,
length: Py_ssize_t,
start: *mut Py_ssize_t,
stop: *mut Py_ssize_t,
step: *mut Py_ssize_t,
slicelength: *mut Py_ssize_t,
) -> c_int;
}
#[cfg(Py_3_7)]
#[inline]
pub unsafe fn PySlice_GetIndicesEx(
slice: *mut PyObject,

View file

@ -165,12 +165,9 @@ extern "C" {
) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8String")]
pub fn PyUnicode_AsUTF8String(unicode: *mut PyObject) -> *mut PyObject;
#[cfg(any(Py_3_10, all(Py_3_7, not(Py_LIMITED_API))))]
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
#[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8AndSize")]
pub fn PyUnicode_AsUTF8AndSize(unicode: *mut PyObject, size: *mut Py_ssize_t) -> *const c_char;
#[cfg(not(any(Py_3_7, Py_LIMITED_API)))]
#[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8AndSize")]
pub fn PyUnicode_AsUTF8AndSize(unicode: *mut PyObject, size: *mut Py_ssize_t) -> *mut c_char;
#[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeUTF32")]
pub fn PyUnicode_DecodeUTF32(
string: *const c_char,

View file

@ -45,20 +45,12 @@ pub(crate) fn gil_is_acquired() -> bool {
/// signal handling depends on the notion of a 'main thread', which must be the thread that
/// initializes the Python interpreter.
///
/// If both the Python interpreter and Python threading are already initialized, this function has
/// no effect.
/// If the Python interpreter is already initialized, this function has no effect.
///
/// This function is unavailable under PyPy because PyPy cannot be embedded in Rust (or any other
/// software). Support for this is tracked on the
/// [PyPy issue tracker](https://foss.heptapod.net/pypy/pypy/-/issues/3286).
///
/// Python 3.6 only: If the Python interpreter is initialized but Python threading is not, this
/// function will initialize Python threading.
///
/// # Panics
/// - Python 3.6 only: If this function needs to initialize Python threading but the calling thread
/// is not the thread which initialized Python, this function will panic.
///
/// # Examples
/// ```rust
/// use pyo3::prelude::*;
@ -69,33 +61,13 @@ pub(crate) fn gil_is_acquired() -> bool {
/// # }
/// ```
#[cfg(not(PyPy))]
#[allow(clippy::collapsible_if)] // for if cfg!
pub fn prepare_freethreaded_python() {
// Protect against race conditions when Python is not yet initialized and multiple threads
// concurrently call 'prepare_freethreaded_python()'. Note that we do not protect against
// concurrent initialization of the Python runtime by other users of the Python C API.
START.call_once_force(|_| unsafe {
if cfg!(not(Py_3_7)) {
// Use call_once_force because if initialization panics, it's okay to try again.
if ffi::Py_IsInitialized() != 0 {
if ffi::PyEval_ThreadsInitialized() == 0 {
// We can only safely initialize threads if this thread holds the GIL.
assert!(
!ffi::PyGILState_GetThisThreadState().is_null(),
"Python threading is not initialized and cannot be initialized by this \
thread, because it is not the thread which initialized Python."
);
ffi::PyEval_InitThreads();
}
} else {
ffi::Py_InitializeEx(0);
ffi::PyEval_InitThreads();
// Release the GIL.
ffi::PyEval_SaveThread();
}
} else if ffi::Py_IsInitialized() == 0 {
// In Python 3.7 and up PyEval_InitThreads is irrelevant.
// Use call_once_force because if initialization panics, it's okay to try again.
if ffi::Py_IsInitialized() == 0 {
ffi::Py_InitializeEx(0);
// Release the GIL.
@ -134,7 +106,6 @@ pub fn prepare_freethreaded_python() {
/// # }
/// ```
#[cfg(not(PyPy))]
#[allow(clippy::collapsible_if)] // for if cfg!
pub unsafe fn with_embedded_python_interpreter<F, R>(f: F) -> R
where
F: for<'p> FnOnce(Python<'p>) -> R,
@ -147,15 +118,7 @@ where
ffi::Py_InitializeEx(0);
// Changed in version 3.7: This function is now called by Py_Initialize(), so you dont have to
// call it yourself anymore.
if cfg!(not(Py_3_7)) {
if ffi::PyEval_ThreadsInitialized() == 0 {
ffi::PyEval_InitThreads();
}
}
// Safe: the GIL is already held because of the Py_IntializeEx call.
// Safety: the GIL is already held because of the Py_IntializeEx call.
let pool = GILPool::new();
// Import the threading module - this ensures that it will associate this thread as the "main"
@ -218,6 +181,15 @@ impl GILGuard {
if #[cfg(all(feature = "auto-initialize", not(PyPy)))] {
prepare_freethreaded_python();
} else {
// This is a "hack" to make running `cargo test` for PyO3 convenient (i.e. no need
// to specify `--features auto-initialize` manually. Tests within the crate itself
// all depend on the auto-initialize feature for conciseness but Cargo does not
// provide a mechanism to specify required features for tests.
if option_env!("CARGO_PRIMARY_PACKAGE").is_some() {
#[cfg(not(PyPy))]
prepare_freethreaded_python();
}
START.call_once_force(|_| unsafe {
// Use call_once_force because if there is a panic because the interpreter is
// not initialized, it's fine for the user to initialize the interpreter and
@ -230,14 +202,6 @@ impl GILGuard {
Consider calling `pyo3::prepare_freethreaded_python()` before attempting \
to use Python APIs."
);
assert_ne!(
ffi::PyEval_ThreadsInitialized(),
0,
"Python threading is not initalized and the `auto-initialize` feature is \
not enabled.\n\n\
Consider calling `pyo3::prepare_freethreaded_python()` before attempting \
to use Python APIs."
);
});
}
}

View file

@ -1,28 +1,4 @@
//! Symbols used to denote deprecated usages of PyO3's proc macros.
#[deprecated(
since = "0.14.0",
note = "use `#[pyo3(name = \"...\")]` instead of `#[name = \"...\"]`"
)]
pub const NAME_ATTRIBUTE: () = ();
#[deprecated(
since = "0.14.0",
note = "use `#[pyfn(m)] #[pyo3(name = \"...\")]` instead of `#[pyfn(m, \"...\")]`"
)]
pub const PYFN_NAME_ARGUMENT: () = ();
#[deprecated(
since = "0.14.0",
note = "use `#[pymodule] #[pyo3(name = \"...\")]` instead of `#[pymodule(...)]`"
)]
pub const PYMODULE_NAME_ARGUMENT: () = ();
#[deprecated(
since = "0.14.0",
note = "use `#[pyo3(text_signature = \"...\")]` instead of `#[text_signature = \"...\"]`"
)]
pub const TEXT_SIGNATURE_ATTRIBUTE: () = ();
#[deprecated(since = "0.15.0", note = "use `fn __call__` instead of `#[call]`")]
pub const CALL_ATTRIBUTE: () = ();

View file

@ -17,6 +17,10 @@ use std::ptr::NonNull;
/// PyO3 is designed in a way that all references to those types are bound
/// to the GIL, which is why you can get a token from all references of those
/// types.
///
/// # Safety
///
/// This trait must only be implemented for types which cannot be accessed without the GIL.
pub unsafe trait PyNativeType: Sized {
/// Returns a GIL marker constrained to the lifetime of this type.
#[inline]
@ -528,6 +532,21 @@ impl<T> Py<T> {
})
}
/// Sets an attribute value.
///
/// This is equivalent to the Python expression `self.attr_name = value`.
pub fn setattr<N, V>(&self, py: Python, attr_name: N, value: V) -> PyResult<()>
where
N: ToPyObject,
V: ToPyObject,
{
attr_name.with_borrowed_ptr(py, move |attr_name| {
value.with_borrowed_ptr(py, |value| unsafe {
err::error_on_minusone(py, ffi::PyObject_SetAttr(self.as_ptr(), attr_name, value))
})
})
}
/// Calls the object.
///
/// This is equivalent to the Python expression `self(*args, **kwargs)`.

View file

@ -102,7 +102,7 @@
//! PyO3 uses `rustc`'s `--cfg` flags to enable or disable code used for different Python versions.
//! If you want to do this for your own crate, you can do so with the [`pyo3-build-config`] crate.
//!
//! - `Py_3_6`, `Py_3_7`, `Py_3_8`, `Py_3_9`, `Py_3_10`: Marks code that is only enabled when
//! - `Py_3_7`, `Py_3_8`, `Py_3_9`, `Py_3_10`: Marks code that is only enabled when
//! compiling for a given minimum Python version.
//! - `Py_LIMITED_API`: Marks code enabled when the `abi3` feature flag is enabled.
//! - `PyPy` - Marks code enabled when compiling for PyPy.
@ -110,8 +110,8 @@
//! # Minimum supported Rust and Python versions
//!
//! PyO3 supports the following software versions:
//! - Python 3.6 and up (CPython and PyPy)
//! - Rust 1.41 and up
//! - Python 3.7 and up (CPython and PyPy)
//! - Rust 1.48 and up
//!
//! # Example: Building a native Python module
//!
@ -360,6 +360,11 @@ pub use pyo3_macros::{pyclass, pyfunction, pymethods, pymodule, pyproto, FromPyO
#[macro_use]
mod macros;
/// Test macro hygiene - this is in the crate since we won't have
/// `pyo3` available in the crate root.
#[cfg(all(test, feature = "macros"))]
mod test_hygiene;
/// Test readme and user guide
#[cfg(doctest)]
pub mod doc_test {

View file

@ -83,7 +83,8 @@ where
slots.push(ffi::Py_tp_free, free as _);
}
if cfg!(Py_3_9) {
#[cfg(Py_3_9)]
{
let members = py_class_members::<T>();
if !members.is_empty() {
slots.push(ffi::Py_tp_members, into_raw(members))
@ -155,7 +156,8 @@ fn tp_init_additional<T: PyClass>(type_object: *mut ffi::PyTypeObject) {
// Setting buffer protocols via slots doesn't work until Python 3.9, so on older versions we
// must manually fixup the type object.
if cfg!(not(Py_3_9)) {
#[cfg(not(Py_3_9))]
{
if let Some(buffer) = T::get_buffer() {
unsafe {
(*(*type_object).tp_as_buffer).bf_getbuffer = buffer.bf_getbuffer;
@ -166,7 +168,8 @@ fn tp_init_additional<T: PyClass>(type_object: *mut ffi::PyTypeObject) {
// Setting tp_dictoffset and tp_weaklistoffset via slots doesn't work until Python 3.9, so on
// older versions again we must fixup the type object.
if cfg!(not(Py_3_9)) {
#[cfg(not(Py_3_9))]
{
// __dict__ support
if let Some(dict_offset) = PyCell::<T>::dict_offset() {
unsafe {
@ -258,12 +261,6 @@ fn py_class_members<T: PyClass>() -> Vec<ffi::structmember::PyMemberDef> {
members
}
// Stub needed since the `if cfg!()` above still compiles contained code.
#[cfg(not(Py_3_9))]
fn py_class_members<T: PyClass>() -> Vec<ffi::structmember::PyMemberDef> {
vec![]
}
const PY_GET_SET_DEF_INIT: ffi::PyGetSetDef = ffi::PyGetSetDef {
name: ptr::null_mut(),
get: None,
@ -272,7 +269,6 @@ const PY_GET_SET_DEF_INIT: ffi::PyGetSetDef = ffi::PyGetSetDef {
closure: ptr::null_mut(),
};
#[allow(clippy::collapsible_if)] // for if cfg!
fn py_class_properties(
is_dummy: bool,
for_each_method_def: &dyn Fn(&mut dyn FnMut(&[PyMethodDefType])),

View file

@ -326,7 +326,7 @@ impl<'py> Python<'py> {
/// py.allow_threads(move || {
/// // An example of an "expensive" Rust calculation
/// let sum = numbers.iter().sum();
///
///
/// Ok(sum)
/// })
/// }
@ -367,26 +367,30 @@ impl<'py> Python<'py> {
F: Send + FnOnce() -> T,
T: Send,
{
// Use a guard pattern to handle reacquiring the GIL, so that the GIL will be reacquired
// even if `f` panics.
struct RestoreGuard {
count: usize,
tstate: *mut ffi::PyThreadState,
}
impl Drop for RestoreGuard {
fn drop(&mut self) {
gil::GIL_COUNT.with(|c| c.set(self.count));
unsafe {
ffi::PyEval_RestoreThread(self.tstate);
}
}
}
// The `Send` bound on the closure prevents the user from
// transferring the `Python` token into the closure.
let count = gil::GIL_COUNT.with(|c| c.replace(0));
let tstate = unsafe { ffi::PyEval_SaveThread() };
// Unwinding right here corrupts the Python interpreter state and leads to weird
// crashes such as stack overflows. We will catch the unwind and resume as soon as
// we've restored the GIL state.
//
// Because we will resume unwinding as soon as the GIL state is fixed, we can assert
// that the closure is unwind safe.
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
// Restore GIL state
gil::GIL_COUNT.with(|c| c.set(count));
unsafe {
ffi::PyEval_RestoreThread(tstate);
}
// Now that the GIL state has been safely reset, we can unwind if a panic was caught.
result.unwrap_or_else(|payload| std::panic::resume_unwind(payload))
let _guard = RestoreGuard { count, tstate };
f()
}
/// Evaluates a Python expression in the given context and returns the result.
@ -546,9 +550,9 @@ impl<'py> Python<'py> {
/// ```rust
/// # use pyo3::Python;
/// Python::with_gil(|py| {
/// // PyO3 supports Python 3.6 and up.
/// assert!(py.version_info() >= (3, 6));
/// assert!(py.version_info() >= (3, 6, 0));
/// // PyO3 supports Python 3.7 and up.
/// assert!(py.version_info() >= (3, 7));
/// assert!(py.version_info() >= (3, 7, 0));
/// });
/// ```
pub fn version_info(self) -> PythonVersionInfo<'py> {
@ -840,6 +844,29 @@ mod tests {
});
}
#[test]
fn test_allow_threads_releases_and_acquires_gil() {
Python::with_gil(|py| {
let b = std::sync::Arc::new(std::sync::Barrier::new(2));
let b2 = b.clone();
std::thread::spawn(move || Python::with_gil(|_| b2.wait()));
py.allow_threads(|| {
// If allow_threads does not release the GIL, this will deadlock because
// the thread spawned above will never be able to acquire the GIL.
b.wait();
});
unsafe {
// If the GIL is not reacquired at the end of allow_threads, this call
// will crash the Python interpreter.
let tstate = ffi::PyEval_SaveThread();
ffi::PyEval_RestoreThread(tstate);
}
});
}
#[test]
fn test_allow_threads_panics_safely() {
Python::with_gil(|py| {
@ -864,10 +891,6 @@ mod tests {
fn test_python_version_info() {
Python::with_gil(|py| {
let version = py.version_info();
#[cfg(Py_3_6)]
assert!(version >= (3, 6));
#[cfg(Py_3_6)]
assert!(version >= (3, 6, 0));
#[cfg(Py_3_7)]
assert!(version >= (3, 7));
#[cfg(Py_3_7)]

29
src/test_hygiene/misc.rs Normal file
View file

@ -0,0 +1,29 @@
#![no_implicit_prelude]
#[derive(crate::FromPyObject)]
#[pyo3(crate = "crate")]
struct Derive1(i32); // newtype case
#[derive(crate::FromPyObject)]
#[pyo3(crate = "crate")]
#[allow(dead_code)]
struct Derive2(i32, i32); // tuple case
#[derive(crate::FromPyObject)]
#[pyo3(crate = "crate")]
#[allow(dead_code)]
struct Derive3 {
f: i32,
g: i32,
} // struct case
#[derive(crate::FromPyObject)]
#[pyo3(crate = "crate")]
#[allow(dead_code)]
enum Derive4 {
A(i32),
B { f: i32 },
} // enum case
crate::create_exception!(mymodule, CustomError, crate::exceptions::PyException);
crate::import_exception!(socket, gaierror);

5
src/test_hygiene/mod.rs Normal file
View file

@ -0,0 +1,5 @@
mod misc;
mod pyclass;
mod pyfunction;
mod pymethods;
mod pymodule;

View file

@ -1,27 +1,30 @@
#![no_implicit_prelude]
#![allow(unused_variables)]
#[::pyo3::pyclass]
#[crate::pyclass]
#[pyo3(crate = "crate")]
#[derive(::std::clone::Clone)]
pub struct Foo;
#[::pyo3::pyclass]
#[crate::pyclass]
#[pyo3(crate = "crate")]
pub struct Foo2;
#[::pyo3::pyclass(
#[crate::pyclass(
name = "ActuallyBar",
freelist = 8,
weakref,
unsendable,
subclass,
extends = ::pyo3::types::PyAny,
extends = crate::types::PyAny,
module = "Spam"
)]
#[pyo3(crate = "crate")]
pub struct Bar {
#[pyo3(get, set)]
a: u8,
#[pyo3(get, set)]
b: Foo,
#[pyo3(get, set)]
c: ::std::option::Option<::pyo3::Py<Foo2>>,
c: ::std::option::Option<crate::Py<Foo2>>,
}

View file

@ -0,0 +1,16 @@
#![no_implicit_prelude]
#![allow(unused_variables)]
#[crate::pyfunction]
#[pyo3(crate = "crate")]
fn do_something(x: i32) -> crate::PyResult<i32> {
::std::result::Result::Ok(x)
}
#[test]
fn invoke_wrap_pyfunction() {
crate::Python::with_gil(|py| {
let func = crate::wrap_pyfunction!(do_something)(py).unwrap();
crate::py_run!(py, func, r#"func(5)"#);
});
}

View file

@ -1,13 +1,16 @@
#![no_implicit_prelude]
#![allow(unused_variables)]
#[::pyo3::pyclass]
#[crate::pyclass]
#[pyo3(crate = "crate")]
pub struct Dummy;
#[::pyo3::pyclass]
#[crate::pyclass]
#[pyo3(crate = "crate")]
pub struct DummyIter;
#[::pyo3::pymethods]
#[crate::pymethods]
#[pyo3(crate = "crate")]
impl Dummy {
//////////////////////
// Basic customization
@ -20,8 +23,8 @@ impl Dummy {
"Dummy"
}
fn __bytes__<'py>(&self, py: ::pyo3::Python<'py>) -> &'py ::pyo3::types::PyBytes {
::pyo3::types::PyBytes::new(py, &[0])
fn __bytes__<'py>(&self, py: crate::Python<'py>) -> &'py crate::types::PyBytes {
crate::types::PyBytes::new(py, &[0])
}
fn __format__(&self, format_spec: ::std::string::String) -> ::std::string::String {
@ -60,11 +63,11 @@ impl Dummy {
// Customizing attribute access
//////////////////////
fn __getattr__(&self, name: ::std::string::String) -> &::pyo3::PyAny {
fn __getattr__(&self, name: ::std::string::String) -> &crate::PyAny {
::std::panic!("unimplemented isn't hygienic before 1.50")
}
fn __getattribute__(&self, name: ::std::string::String) -> &::pyo3::PyAny {
fn __getattribute__(&self, name: ::std::string::String) -> &crate::PyAny {
::std::panic!("unimplemented isn't hygienic before 1.50")
}
@ -72,8 +75,8 @@ impl Dummy {
fn __delattr__(&mut self, name: ::std::string::String) {}
fn __dir__<'py>(&self, py: ::pyo3::Python<'py>) -> &'py ::pyo3::types::PyList {
::pyo3::types::PyList::new(py, ::std::vec![0_u8])
fn __dir__<'py>(&self, py: crate::Python<'py>) -> &'py crate::types::PyList {
crate::types::PyList::new(py, ::std::vec![0_u8])
}
//////////////////////
@ -82,17 +85,17 @@ impl Dummy {
fn __get__(
&self,
instance: &::pyo3::PyAny,
owner: &::pyo3::PyAny,
) -> ::pyo3::PyResult<&::pyo3::PyAny> {
instance: &crate::PyAny,
owner: &crate::PyAny,
) -> crate::PyResult<&crate::PyAny> {
::std::panic!("unimplemented isn't hygienic before 1.50")
}
fn __set__(&self, instance: &::pyo3::PyAny, owner: &::pyo3::PyAny) {}
fn __set__(&self, instance: &crate::PyAny, owner: &crate::PyAny) {}
fn __delete__(&self, instance: &::pyo3::PyAny) {}
fn __delete__(&self, instance: &crate::PyAny) {}
fn __set_name__(&self, owner: &::pyo3::PyAny, name: &::pyo3::PyAny) {}
fn __set_name__(&self, owner: &crate::PyAny, name: &crate::PyAny) {}
//////////////////////
// Implementing Descriptors
@ -102,24 +105,24 @@ impl Dummy {
0
}
fn __getitem__(&self, key: u32) -> ::pyo3::PyResult<u32> {
::std::result::Result::Err(::pyo3::exceptions::PyKeyError::new_err("boo"))
fn __getitem__(&self, key: u32) -> crate::PyResult<u32> {
::std::result::Result::Err(crate::exceptions::PyKeyError::new_err("boo"))
}
fn __setitem__(&self, key: u32, value: u32) {}
fn __delitem__(&self, key: u32) {}
fn __iter__(_: ::pyo3::pycell::PyRef<Self>, py: ::pyo3::Python) -> ::pyo3::Py<DummyIter> {
::pyo3::Py::new(py, DummyIter {}).unwrap()
fn __iter__(_: crate::pycell::PyRef<Self>, py: crate::Python) -> crate::Py<DummyIter> {
crate::Py::new(py, DummyIter {}).unwrap()
}
fn __next__(&mut self) -> ::std::option::Option<()> {
::std::option::Option::None
}
fn __reversed__(slf: ::pyo3::pycell::PyRef<Self>, py: ::pyo3::Python) -> ::pyo3::Py<DummyIter> {
::pyo3::Py::new(py, DummyIter {}).unwrap()
fn __reversed__(slf: crate::pycell::PyRef<Self>, py: crate::Python) -> crate::Py<DummyIter> {
crate::Py::new(py, DummyIter {}).unwrap()
}
fn __contains__(&self, item: u32) -> bool {
@ -142,12 +145,12 @@ impl Dummy {
Dummy {}
}
fn __truediv__(&self, _other: &Self) -> ::pyo3::PyResult<()> {
::std::result::Result::Err(::pyo3::exceptions::PyZeroDivisionError::new_err("boo"))
fn __truediv__(&self, _other: &Self) -> crate::PyResult<()> {
::std::result::Result::Err(crate::exceptions::PyZeroDivisionError::new_err("boo"))
}
fn __floordiv__(&self, _other: &Self) -> ::pyo3::PyResult<()> {
::std::result::Result::Err(::pyo3::exceptions::PyZeroDivisionError::new_err("boo"))
fn __floordiv__(&self, _other: &Self) -> crate::PyResult<()> {
::std::result::Result::Err(crate::exceptions::PyZeroDivisionError::new_err("boo"))
}
fn __mod__(&self, _other: &Self) -> u32 {
@ -194,12 +197,12 @@ impl Dummy {
Dummy {}
}
fn __rtruediv__(&self, _other: &Self) -> ::pyo3::PyResult<()> {
::std::result::Result::Err(::pyo3::exceptions::PyZeroDivisionError::new_err("boo"))
fn __rtruediv__(&self, _other: &Self) -> crate::PyResult<()> {
::std::result::Result::Err(crate::exceptions::PyZeroDivisionError::new_err("boo"))
}
fn __rfloordiv__(&self, _other: &Self) -> ::pyo3::PyResult<()> {
::std::result::Result::Err(::pyo3::exceptions::PyZeroDivisionError::new_err("boo"))
fn __rfloordiv__(&self, _other: &Self) -> crate::PyResult<()> {
::std::result::Result::Err(crate::exceptions::PyZeroDivisionError::new_err("boo"))
}
fn __rmod__(&self, _other: &Self) -> u32 {
@ -258,24 +261,24 @@ impl Dummy {
fn __ior__(&mut self, other: &Self) {}
fn __neg__(slf: ::pyo3::pycell::PyRef<Self>) -> ::pyo3::pycell::PyRef<Self> {
fn __neg__(slf: crate::pycell::PyRef<Self>) -> crate::pycell::PyRef<Self> {
slf
}
fn __pos__(slf: ::pyo3::pycell::PyRef<Self>) -> ::pyo3::pycell::PyRef<Self> {
fn __pos__(slf: crate::pycell::PyRef<Self>) -> crate::pycell::PyRef<Self> {
slf
}
fn __abs__(slf: ::pyo3::pycell::PyRef<Self>) -> ::pyo3::pycell::PyRef<Self> {
fn __abs__(slf: crate::pycell::PyRef<Self>) -> crate::pycell::PyRef<Self> {
slf
}
fn __invert__(slf: ::pyo3::pycell::PyRef<Self>) -> ::pyo3::pycell::PyRef<Self> {
fn __invert__(slf: crate::pycell::PyRef<Self>) -> crate::pycell::PyRef<Self> {
slf
}
fn __complex__<'py>(&self, py: ::pyo3::Python<'py>) -> &'py ::pyo3::types::PyComplex {
::pyo3::types::PyComplex::from_doubles(py, 0.0, 0.0)
fn __complex__<'py>(&self, py: crate::Python<'py>) -> &'py crate::types::PyComplex {
crate::types::PyComplex::from_doubles(py, 0.0, 0.0)
}
fn __int__(&self) -> u32 {
@ -314,9 +317,9 @@ impl Dummy {
fn __exit__(
&mut self,
exc_type: &::pyo3::PyAny,
exc_value: &::pyo3::PyAny,
traceback: &::pyo3::PyAny,
exc_type: &crate::PyAny,
exc_value: &crate::PyAny,
traceback: &crate::PyAny,
) {
}
@ -324,7 +327,7 @@ impl Dummy {
// Awaitable Objects
//////////////////////
fn __await__(slf: ::pyo3::pycell::PyRef<Self>) -> ::pyo3::pycell::PyRef<Self> {
fn __await__(slf: crate::pycell::PyRef<Self>) -> crate::pycell::PyRef<Self> {
slf
}
@ -333,8 +336,8 @@ impl Dummy {
// Asynchronous Iterators
//////////////////////
fn __aiter__(slf: ::pyo3::pycell::PyRef<Self>, py: ::pyo3::Python) -> ::pyo3::Py<DummyIter> {
::pyo3::Py::new(py, DummyIter {}).unwrap()
fn __aiter__(slf: crate::pycell::PyRef<Self>, py: crate::Python) -> crate::Py<DummyIter> {
crate::Py::new(py, DummyIter {}).unwrap()
}
fn __anext__(&mut self) -> ::std::option::Option<()> {
@ -349,9 +352,9 @@ impl Dummy {
fn __aexit__(
&mut self,
exc_type: &::pyo3::PyAny,
exc_value: &::pyo3::PyAny,
traceback: &::pyo3::PyAny,
exc_type: &crate::PyAny,
exc_value: &crate::PyAny,
traceback: &crate::PyAny,
) {
}
@ -362,13 +365,13 @@ impl Dummy {
#[staticmethod]
fn staticmethod() {}
#[classmethod]
fn clsmethod(_: &::pyo3::types::PyType) {}
fn clsmethod(_: &crate::types::PyType) {}
#[args(args = "*", kwds = "**")]
fn __call__(
&self,
_args: &::pyo3::types::PyTuple,
_kwds: ::std::option::Option<&::pyo3::types::PyDict>,
) -> ::pyo3::PyResult<i32> {
_args: &crate::types::PyTuple,
_kwds: ::std::option::Option<&crate::types::PyDict>,
) -> crate::PyResult<i32> {
::std::panic!("unimplemented isn't hygienic before 1.50")
}
#[new]
@ -391,8 +394,8 @@ impl Dummy {
fn __richcmp__(
&self,
other: &Self,
op: ::pyo3::class::basic::CompareOp,
) -> ::pyo3::PyResult<bool> {
op: crate::class::basic::CompareOp,
) -> crate::PyResult<bool> {
::std::result::Result::Ok(false)
}
// PyGcProtocol

View file

@ -0,0 +1,23 @@
#![no_implicit_prelude]
#![allow(unused_variables)]
#[crate::pyfunction]
#[pyo3(crate = "crate")]
fn do_something(x: i32) -> crate::PyResult<i32> {
::std::result::Result::Ok(x)
}
#[crate::pymodule]
#[pyo3(crate = "crate")]
fn foo(_py: crate::Python, _m: &crate::types::PyModule) -> crate::PyResult<()> {
::std::result::Result::Ok(())
}
#[crate::pymodule]
#[pyo3(crate = "crate")]
fn my_module(_py: crate::Python, m: &crate::types::PyModule) -> crate::PyResult<()> {
m.add_function(crate::wrap_pyfunction!(do_something, m)?)?;
m.add_wrapped(crate::wrap_pymodule!(foo))?;
::std::result::Result::Ok(())
}

View file

@ -16,6 +16,10 @@ use std::thread::{self, ThreadId};
/// is of `PyAny`.
///
/// This trait is intended to be used internally.
///
/// # Safety
///
/// This trait must only be implemented for types which represent valid layouts of Python objects.
pub unsafe trait PyLayout<T> {}
/// `T: PySizedLayout<U>` represents that `T` is not a instance of
@ -31,6 +35,11 @@ pub trait PySizedLayout<T>: PyLayout<T> + Sized {}
/// - the return value of type_object must always point to the same PyTypeObject instance
///
/// It is safely implemented by the `pyclass` macro.
///
/// # Safety
///
/// Implementations must provide an implementation for `type_object_raw` which infallibly produces a
/// non-null pointer to the corresponding Python type object.
pub unsafe trait PyTypeInfo: Sized {
/// Class name.
const NAME: &'static str;
@ -57,6 +66,8 @@ pub unsafe trait PyTypeInfo: Sized {
/// Python object types that have a corresponding type object.
///
/// # Safety
///
/// This trait is marked unsafe because not fulfilling the contract for type_object
/// leads to UB.
///

View file

@ -74,7 +74,7 @@ impl PyAny {
///
/// Python::with_gil(|py| {
/// let dict = PyDict::new(py);
/// assert!(dict.is_instance::<PyAny>().unwrap());
/// assert!(dict.is_instance_of::<PyAny>().unwrap());
/// let any: &PyAny = dict.as_ref();
/// assert!(any.downcast::<PyDict>().is_ok());
/// assert!(any.downcast::<PyList>().is_err());
@ -665,11 +665,21 @@ impl PyAny {
unsafe { self.py().from_owned_ptr(ffi::PyObject_Dir(self.as_ptr())) }
}
/// Checks whether this object is an instance of type `typ`.
///
/// This is equivalent to the Python expression `isinstance(self, typ)`.
pub fn is_instance(&self, typ: &PyType) -> PyResult<bool> {
let result = unsafe { ffi::PyObject_IsInstance(self.as_ptr(), typ.as_ptr()) };
err::error_on_minusone(self.py(), result)?;
Ok(result == 1)
}
/// Checks whether this object is an instance of type `T`.
///
/// This is equivalent to the Python expression `isinstance(self, T)`.
pub fn is_instance<T: PyTypeObject>(&self) -> PyResult<bool> {
T::type_object(self.py()).is_instance(self)
/// This is equivalent to the Python expression `isinstance(self, T)`,
/// if the type `T` is known at compile time.
pub fn is_instance_of<T: PyTypeObject>(&self) -> PyResult<bool> {
self.is_instance(T::type_object(self.py()))
}
/// Returns a GIL marker constrained to the lifetime of this type.
@ -682,17 +692,11 @@ impl PyAny {
#[cfg(test)]
mod tests {
use crate::{
type_object::PyTypeObject,
types::{IntoPyDict, PyList, PyLong, PyModule},
Python, ToPyObject,
};
macro_rules! test_module {
($py:ident, $code:literal) => {
PyModule::from_code($py, indoc::indoc!($code), file!(), "test_module")
.expect("module creation failed")
};
}
#[test]
fn test_call_for_non_existing_method() {
Python::with_gil(|py| {
@ -717,14 +721,17 @@ mod tests {
#[test]
fn test_call_method0() {
Python::with_gil(|py| {
let module = test_module!(
let module = PyModule::from_code(
py,
r#"
class SimpleClass:
def foo(self):
return 42
"#
);
class SimpleClass:
def foo(self):
return 42
"#,
file!(),
"test_module",
)
.expect("module creation failed");
let simple_class = module.getattr("SimpleClass").unwrap().call0().unwrap();
assert_eq!(
@ -776,10 +783,18 @@ mod tests {
fn test_any_isinstance() {
Python::with_gil(|py| {
let x = 5.to_object(py).into_ref(py);
assert!(x.is_instance::<PyLong>().unwrap());
assert!(x.is_instance_of::<PyLong>().unwrap());
let l = vec![x, x].to_object(py).into_ref(py);
assert!(l.is_instance::<PyList>().unwrap());
assert!(l.is_instance_of::<PyList>().unwrap());
});
}
#[test]
fn test_any_isinstance_of() {
Python::with_gil(|py| {
let l = vec![1u8, 2].to_object(py).into_ref(py);
assert!(l.is_instance(PyList::type_object(py)).unwrap());
});
}
}

View file

@ -241,7 +241,7 @@ mod tests {
fn test_from_err() {
Python::with_gil(|py| {
if let Err(err) = PyByteArray::from(py, &py.None()) {
assert!(err.is_instance::<exceptions::PyTypeError>(py));
assert!(err.is_instance_of::<exceptions::PyTypeError>(py));
} else {
panic!("error");
}
@ -293,7 +293,7 @@ mod tests {
assert!(py_bytearray_result
.err()
.unwrap()
.is_instance::<PyValueError>(py));
.is_instance_of::<PyValueError>(py));
})
}
}

View file

@ -174,7 +174,7 @@ mod tests {
assert!(py_bytes_result
.err()
.unwrap()
.is_instance::<PyValueError>(py));
.is_instance_of::<PyValueError>(py));
});
}
}

412
src/types/capsule.rs Normal file
View file

@ -0,0 +1,412 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::Python;
use crate::{ffi, AsPyPointer, PyAny};
use crate::{pyobject_native_type_core, PyErr, PyResult};
use std::ffi::{c_void, CStr};
use std::os::raw::c_int;
/// Represents a Python Capsule
/// as described in [Capsules](https://docs.python.org/3/c-api/capsule.html#capsules):
/// > This subtype of PyObject represents an opaque value, useful for C extension
/// > modules who need to pass an opaque value (as a void* pointer) through Python
/// > code to other C code. It is often used to make a C function pointer defined
/// > in one module available to other modules, so the regular import mechanism can
/// > be used to access C APIs defined in dynamically loaded modules.
///
///
/// # Example
/// ```
/// use std::ffi::CString;
/// use pyo3::{prelude::*, types::PyCapsule};
///
/// #[repr(C)]
/// struct Foo {
/// pub val: u32,
/// }
///
/// let r = Python::with_gil(|py| -> PyResult<()> {
/// let foo = Foo { val: 123 };
/// let name = CString::new("builtins.capsule").unwrap();
///
/// let capsule = PyCapsule::new(py, foo, name.as_ref())?;
///
/// let module = PyModule::import(py, "builtins")?;
/// module.add("capsule", capsule)?;
///
/// let cap: &Foo = unsafe { PyCapsule::import(py, name.as_ref())? };
/// assert_eq!(cap.val, 123);
/// Ok(())
/// });
/// assert!(r.is_ok());
/// ```
#[repr(transparent)]
pub struct PyCapsule(PyAny);
pyobject_native_type_core!(PyCapsule, ffi::PyCapsule_Type, #checkfunction=ffi::PyCapsule_CheckExact);
impl PyCapsule {
/// Constructs a new capsule whose contents are `value`, associated with `name`.
/// `name` is the identifier for the capsule; if it is stored as an attribute of a module,
/// the name should be in the format `"modulename.attribute"`.
///
/// It is checked at compile time that the type T is not zero-sized. Rust function items
/// need to be cast to a function pointer (`fn(args) -> result`) to be put into a capsule.
///
/// # Example
///
/// ```
/// use pyo3::{prelude::*, types::PyCapsule};
/// use std::ffi::CString;
///
/// Python::with_gil(|py| {
/// let name = CString::new("foo").unwrap();
/// let capsule = PyCapsule::new(py, 123_u32, &name).unwrap();
/// let val = unsafe { capsule.reference::<u32>() };
/// assert_eq!(*val, 123);
/// });
/// ```
///
/// However, attempting to construct a `PyCapsule` with a zero-sized type will not compile:
///
/// ```compile_fail
/// use pyo3::{prelude::*, types::PyCapsule};
/// use std::ffi::CString;
///
/// Python::with_gil(|py| {
/// let name = CString::new("foo").unwrap();
/// let capsule = PyCapsule::new(py, (), &name).unwrap(); // Oops! `()` is zero sized!
/// });
/// ```
pub fn new<'py, T: 'static + Send + AssertNotZeroSized>(
py: Python<'py>,
value: T,
name: &CStr,
) -> PyResult<&'py Self> {
Self::new_with_destructor(py, value, name, |_, _| {})
}
/// Constructs a new capsule whose contents are `value`, associated with `name`.
///
/// Also provides a destructor: when the `PyCapsule` is destroyed, it will be passed the original object,
/// as well as a `*mut c_void` which will point to the capsule's context, if any.
pub fn new_with_destructor<
'py,
T: 'static + Send + AssertNotZeroSized,
F: FnOnce(T, *mut c_void),
>(
py: Python<'py>,
value: T,
name: &CStr,
destructor: F,
) -> PyResult<&'py Self> {
AssertNotZeroSized::assert_not_zero_sized(&value);
let val = Box::new(CapsuleContents { value, destructor });
let cap_ptr = unsafe {
ffi::PyCapsule_New(
Box::into_raw(val) as *mut c_void,
name.as_ptr(),
Some(capsule_destructor::<T, F>),
)
};
unsafe { py.from_owned_ptr_or_err(cap_ptr) }
}
/// Imports an existing capsule.
///
/// The `name` should match the path to the module attribute exactly in the form
/// of `"module.attribute"`, which should be the same as the name within the capsule.
///
/// # Safety
///
/// It must be known that this capsule's value pointer is to an item of type `T`.
pub unsafe fn import<'py, T>(py: Python<'py>, name: &CStr) -> PyResult<&'py T> {
let ptr = ffi::PyCapsule_Import(name.as_ptr(), false as c_int);
if ptr.is_null() {
Err(PyErr::fetch(py))
} else {
Ok(&*(ptr as *const T))
}
}
/// Sets the context pointer in the capsule.
///
/// # Notes
///
/// The context is treated much like the value of the capsule, but should likely act as
/// a place to store any state management when using the capsule.
///
/// If you want to store a Rust value as the context, and drop it from the destructor, use
/// `Box::into_raw` to convert it into a pointer, see the example.
///
/// # Example
///
/// ```
/// use std::ffi::CString;
/// use std::sync::mpsc::{channel, Sender};
/// use libc::c_void;
/// use pyo3::{prelude::*, types::PyCapsule};
///
/// let (tx, rx) = channel::<String>();
///
/// fn destructor(val: u32, context: *mut c_void) {
/// let ctx = unsafe { *Box::from_raw(context as *mut Sender<String>) };
/// ctx.send("Destructor called!".to_string()).unwrap();
/// }
///
/// Python::with_gil(|py| {
/// let name = CString::new("foo").unwrap();
/// let capsule =
/// PyCapsule::new_with_destructor(py, 123, &name, destructor as fn(u32, *mut c_void))
/// .unwrap();
/// let context = Box::new(tx); // `Sender<String>` is our context, box it up and ship it!
/// capsule.set_context(py, Box::into_raw(context) as *mut c_void).unwrap();
/// // This scope will end, causing our destructor to be called...
/// });
///
/// assert_eq!(rx.recv(), Ok("Destructor called!".to_string()));
/// ```
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn set_context(&self, py: Python, context: *mut c_void) -> PyResult<()> {
let result = unsafe { ffi::PyCapsule_SetContext(self.as_ptr(), context) as u8 };
if result != 0 {
Err(PyErr::fetch(py))
} else {
Ok(())
}
}
/// Gets the current context stored in the capsule. If there is no context, the pointer
/// will be null.
pub fn get_context(&self, py: Python) -> PyResult<*mut c_void> {
let ctx = unsafe { ffi::PyCapsule_GetContext(self.as_ptr()) };
if ctx.is_null() && self.is_valid() && PyErr::occurred(py) {
Err(PyErr::fetch(py))
} else {
Ok(ctx)
}
}
/// Obtains a reference to the value of this capsule.
///
/// # Safety
///
/// It must be known that this capsule's pointer is to an item of type `T`.
pub unsafe fn reference<T>(&self) -> &T {
&*(self.pointer() as *const T)
}
/// Gets the raw `c_void` pointer to the value in this capsule.
pub fn pointer(&self) -> *mut c_void {
unsafe { ffi::PyCapsule_GetPointer(self.0.as_ptr(), self.name().as_ptr()) }
}
/// Checks if this is a valid capsule.
pub fn is_valid(&self) -> bool {
let r = unsafe { ffi::PyCapsule_IsValid(self.as_ptr(), self.name().as_ptr()) } as u8;
r != 0
}
/// Retrieves the name of this capsule.
pub fn name(&self) -> &CStr {
unsafe {
let ptr = ffi::PyCapsule_GetName(self.as_ptr());
CStr::from_ptr(ptr)
}
}
}
// C layout, as PyCapsule::get_reference depends on `T` being first.
#[repr(C)]
struct CapsuleContents<T: 'static + Send, D: FnOnce(T, *mut c_void)> {
value: T,
destructor: D,
}
// Wrapping ffi::PyCapsule_Destructor for a user supplied FnOnce(T) for capsule destructor
unsafe extern "C" fn capsule_destructor<T: 'static + Send, F: FnOnce(T, *mut c_void)>(
capsule: *mut ffi::PyObject,
) {
let ptr = ffi::PyCapsule_GetPointer(capsule, ffi::PyCapsule_GetName(capsule));
let ctx = ffi::PyCapsule_GetContext(capsule);
let CapsuleContents { value, destructor } = *Box::from_raw(ptr as *mut CapsuleContents<T, F>);
destructor(value, ctx)
}
/// Guarantee `T` is not zero sized at compile time.
// credit: `<https://users.rust-lang.org/t/is-it-possible-to-assert-at-compile-time-that-foo-t-is-not-called-with-a-zst/67685>`
#[doc(hidden)]
pub trait AssertNotZeroSized: Sized {
const _CONDITION: usize = (std::mem::size_of::<Self>() == 0) as usize;
const _CHECK: &'static str =
["PyCapsule value type T must not be zero-sized!"][Self::_CONDITION];
#[allow(path_statements, clippy::no_effect)]
fn assert_not_zero_sized(&self) {
<Self as AssertNotZeroSized>::_CHECK;
}
}
impl<T> AssertNotZeroSized for T {}
#[cfg(test)]
mod tests {
use libc::c_void;
use crate::prelude::PyModule;
use crate::{types::PyCapsule, Py, PyResult, Python};
use std::ffi::CString;
use std::sync::mpsc::{channel, Sender};
#[test]
fn test_pycapsule_struct() -> PyResult<()> {
#[repr(C)]
struct Foo {
pub val: u32,
}
impl Foo {
fn get_val(&self) -> u32 {
self.val
}
}
Python::with_gil(|py| -> PyResult<()> {
let foo = Foo { val: 123 };
let name = CString::new("foo").unwrap();
let cap = PyCapsule::new(py, foo, &name)?;
assert!(cap.is_valid());
let foo_capi = unsafe { cap.reference::<Foo>() };
assert_eq!(foo_capi.val, 123);
assert_eq!(foo_capi.get_val(), 123);
assert_eq!(cap.name(), name.as_ref());
Ok(())
})
}
#[test]
fn test_pycapsule_func() {
fn foo(x: u32) -> u32 {
x
}
let cap: Py<PyCapsule> = Python::with_gil(|py| {
let name = CString::new("foo").unwrap();
let cap = PyCapsule::new(py, foo as fn(u32) -> u32, &name).unwrap();
cap.into()
});
Python::with_gil(|py| {
let f = unsafe { cap.as_ref(py).reference::<fn(u32) -> u32>() };
assert_eq!(f(123), 123);
});
}
#[test]
fn test_pycapsule_context() -> PyResult<()> {
Python::with_gil(|py| {
let name = CString::new("foo").unwrap();
let cap = PyCapsule::new(py, 0, &name)?;
let c = cap.get_context(py)?;
assert!(c.is_null());
let ctx = Box::new(123_u32);
cap.set_context(py, Box::into_raw(ctx) as _)?;
let ctx_ptr: *mut c_void = cap.get_context(py)?;
let ctx = unsafe { *Box::from_raw(ctx_ptr as *mut u32) };
assert_eq!(ctx, 123);
Ok(())
})
}
#[test]
fn test_pycapsule_import() -> PyResult<()> {
#[repr(C)]
struct Foo {
pub val: u32,
}
Python::with_gil(|py| -> PyResult<()> {
let foo = Foo { val: 123 };
let name = CString::new("builtins.capsule").unwrap();
let capsule = PyCapsule::new(py, foo, &name)?;
let module = PyModule::import(py, "builtins")?;
module.add("capsule", capsule)?;
// check error when wrong named passed for capsule.
let wrong_name = CString::new("builtins.non_existant").unwrap();
let result: PyResult<&Foo> = unsafe { PyCapsule::import(py, wrong_name.as_ref()) };
assert!(result.is_err());
// corret name is okay.
let cap: &Foo = unsafe { PyCapsule::import(py, name.as_ref())? };
assert_eq!(cap.val, 123);
Ok(())
})
}
#[test]
fn test_vec_storage() {
let cap: Py<PyCapsule> = Python::with_gil(|py| {
let name = CString::new("foo").unwrap();
let stuff: Vec<u8> = vec![1, 2, 3, 4];
let cap = PyCapsule::new(py, stuff, &name).unwrap();
cap.into()
});
Python::with_gil(|py| {
let ctx: &Vec<u8> = unsafe { cap.as_ref(py).reference() };
assert_eq!(ctx, &[1, 2, 3, 4]);
})
}
#[test]
fn test_vec_context() {
let context: Vec<u8> = vec![1, 2, 3, 4];
let cap: Py<PyCapsule> = Python::with_gil(|py| {
let name = CString::new("foo").unwrap();
let cap = PyCapsule::new(py, 0, &name).unwrap();
cap.set_context(py, Box::into_raw(Box::new(&context)) as _)
.unwrap();
cap.into()
});
Python::with_gil(|py| {
let ctx_ptr: *mut c_void = cap.as_ref(py).get_context(py).unwrap();
let ctx = unsafe { *Box::from_raw(ctx_ptr as *mut &Vec<u8>) };
assert_eq!(ctx, &vec![1_u8, 2, 3, 4]);
})
}
#[test]
fn test_pycapsule_destructor() {
let (tx, rx) = channel::<bool>();
fn destructor(_val: u32, ctx: *mut c_void) {
assert!(!ctx.is_null());
let context = unsafe { *Box::from_raw(ctx as *mut Sender<bool>) };
context.send(true).unwrap();
}
Python::with_gil(|py| {
let name = CString::new("foo").unwrap();
let cap =
PyCapsule::new_with_destructor(py, 0, &name, destructor as fn(u32, *mut c_void))
.unwrap();
cap.set_context(py, Box::into_raw(Box::new(tx)) as _)
.unwrap();
});
// the destructor was called.
assert_eq!(rx.recv(), Ok(true));
}
}

View file

@ -113,7 +113,6 @@ mod tests {
#[cfg(any(not(Py_LIMITED_API), Py_3_8))]
use crate::PyTryFrom;
use crate::{Py, PyAny, Python, ToPyObject};
use indoc::indoc;
#[test]
fn vec_iter() {
@ -177,16 +176,14 @@ mod tests {
#[test]
fn fibonacci_generator() {
let fibonacci_generator = indoc!(
r#"
def fibonacci(target):
a = 1
b = 1
for _ in range(target):
yield a
a, b = b, a + b
"#
);
let fibonacci_generator = r#"
def fibonacci(target):
a = 1
b = 1
for _ in range(target):
yield a
a, b = b, a + b
"#;
Python::with_gil(|py| {
let context = PyDict::new(py);
@ -206,7 +203,7 @@ mod tests {
let x = 5.to_object(py);
let err = PyIterator::from_object(py, &x).unwrap_err();
assert!(err.is_instance::<PyTypeError>(py));
assert!(err.is_instance_of::<PyTypeError>(py));
});
}

View file

@ -180,7 +180,7 @@ mod tests {
assert!(mapping
.get_item(8i32)
.unwrap_err()
.is_instance::<PyKeyError>(py));
.is_instance_of::<PyKeyError>(py));
});
}
@ -216,7 +216,7 @@ mod tests {
assert!(mapping
.get_item(7i32)
.unwrap_err()
.is_instance::<PyKeyError>(py));
.is_instance_of::<PyKeyError>(py));
});
}

View file

@ -6,6 +6,7 @@ pub use self::any::PyAny;
pub use self::boolobject::PyBool;
pub use self::bytearray::PyByteArray;
pub use self::bytes::PyBytes;
pub use self::capsule::PyCapsule;
pub use self::complex::PyComplex;
#[cfg(not(Py_LIMITED_API))]
pub use self::datetime::{
@ -26,6 +27,7 @@ pub use self::slice::{PySlice, PySliceIndices};
#[cfg(all(not(Py_LIMITED_API), target_endian = "little"))]
pub use self::string::PyStringData;
pub use self::string::{PyString, PyString as PyUnicode};
pub use self::traceback::PyTraceback;
pub use self::tuple::PyTuple;
pub use self::typeobject::PyType;
@ -222,6 +224,7 @@ mod any;
mod boolobject;
mod bytearray;
mod bytes;
mod capsule;
mod complex;
#[cfg(not(Py_LIMITED_API))]
mod datetime;
@ -237,5 +240,6 @@ mod sequence;
mod set;
mod slice;
mod string;
mod traceback;
mod tuple;
mod typeobject;

View file

@ -8,9 +8,9 @@ use crate::exceptions;
use crate::ffi;
use crate::pyclass::PyClass;
use crate::type_object::PyTypeObject;
use crate::types::PyCFunction;
use crate::types::{PyAny, PyDict, PyList};
use crate::types::{PyCFunction, PyTuple};
use crate::{AsPyPointer, IntoPy, Py, PyObject, Python};
use crate::{AsPyPointer, IntoPy, PyObject, Python};
use std::ffi::{CStr, CString};
use std::str;
@ -146,7 +146,7 @@ impl PyModule {
match self.getattr("__all__") {
Ok(idx) => idx.downcast().map_err(PyErr::from),
Err(err) => {
if err.is_instance::<exceptions::PyAttributeError>(self.py()) {
if err.is_instance_of::<exceptions::PyAttributeError>(self.py()) {
let l = PyList::empty(self.py());
self.setattr("__all__", l).map_err(PyErr::from)?;
Ok(l)
@ -367,46 +367,6 @@ impl PyModule {
let name = fun.getattr("__name__")?.extract()?;
self.add(name, fun)
}
/// Calls a function in the module.
///
/// This is equivalent to the Python expression `module.name(*args, **kwargs)`.
#[deprecated(
since = "0.14.0",
note = "use getattr(name)?.call(args, kwargs) instead"
)]
pub fn call(
&self,
name: &str,
args: impl IntoPy<Py<PyTuple>>,
kwargs: Option<&PyDict>,
) -> PyResult<&PyAny> {
self.getattr(name)?.call(args, kwargs)
}
/// Calls a function in the module with only positional arguments.
///
/// This is equivalent to the Python expression `module.name(*args)`.
#[deprecated(since = "0.14.0", note = "use getattr(name)?.call1(args) instead")]
pub fn call1(&self, name: &str, args: impl IntoPy<Py<PyTuple>>) -> PyResult<&PyAny> {
self.getattr(name)?.call1(args)
}
/// Calls a function in the module without arguments.
///
/// This is equivalent to the Python expression `module.name()`.
#[deprecated(since = "0.14.0", note = "use getattr(name)?.call0() instead")]
pub fn call0(&self, name: &str) -> PyResult<&PyAny> {
self.getattr(name)?.call0()
}
/// Gets a member from the module.
///
/// This is equivalent to the Python expression `module.name`.
#[deprecated(since = "0.14.0", note = "use getattr(name) instead")]
pub fn get(&self, name: &str) -> PyResult<&PyAny> {
self.getattr(name)
}
}
#[cfg(test)]

View file

@ -278,6 +278,7 @@ fn err_if_invalid_value<T: PartialEq>(
#[cfg(test)]
mod test_128bit_intergers {
use super::*;
use crate::types::PyDict;
#[cfg(not(target_arch = "wasm32"))]
use proptest::prelude::*;
@ -288,7 +289,9 @@ mod test_128bit_intergers {
fn test_i128_roundtrip(x: i128) {
Python::with_gil(|py| {
let x_py = x.into_py(py);
crate::py_run!(py, x_py, &format!("assert x_py == {}", x));
let locals = PyDict::new(py);
locals.set_item("x_py", x_py.clone_ref(py)).unwrap();
py.run(&format!("assert x_py == {}", x), None, Some(locals)).unwrap();
let roundtripped: i128 = x_py.extract(py).unwrap();
assert_eq!(x, roundtripped);
})
@ -301,7 +304,9 @@ mod test_128bit_intergers {
fn test_u128_roundtrip(x: u128) {
Python::with_gil(|py| {
let x_py = x.into_py(py);
crate::py_run!(py, x_py, &format!("assert x_py == {}", x));
let locals = PyDict::new(py);
locals.set_item("x_py", x_py.clone_ref(py)).unwrap();
py.run(&format!("assert x_py == {}", x), None, Some(locals)).unwrap();
let roundtripped: u128 = x_py.extract(py).unwrap();
assert_eq!(x, roundtripped);
})
@ -345,7 +350,7 @@ mod test_128bit_intergers {
Python::with_gil(|py| {
let obj = py.eval("(1 << 130) * -1", None, None).unwrap();
let err = obj.extract::<i128>().unwrap_err();
assert!(err.is_instance::<crate::exceptions::PyOverflowError>(py));
assert!(err.is_instance_of::<crate::exceptions::PyOverflowError>(py));
})
}
@ -354,7 +359,7 @@ mod test_128bit_intergers {
Python::with_gil(|py| {
let obj = py.eval("1 << 130", None, None).unwrap();
let err = obj.extract::<u128>().unwrap_err();
assert!(err.is_instance::<crate::exceptions::PyOverflowError>(py));
assert!(err.is_instance_of::<crate::exceptions::PyOverflowError>(py));
})
}
}
@ -421,7 +426,7 @@ mod tests {
let obj = ("123").to_object(py);
let err = obj.extract::<$t>(py).unwrap_err();
assert!(err.is_instance::<exceptions::PyTypeError>(py));
assert!(err.is_instance_of::<exceptions::PyTypeError>(py));
});
}
@ -431,7 +436,7 @@ mod tests {
let obj = (12.3).to_object(py);
let err = obj.extract::<$t>(py).unwrap_err();
assert!(err.is_instance::<exceptions::PyTypeError>(py));});
assert!(err.is_instance_of::<exceptions::PyTypeError>(py));});
}
#[test]

Some files were not shown because too many files have changed in this diff Show more