Merge branch 'main' into immutable

This commit is contained in:
Bruno Kolenbrander 2021-11-22 10:08:51 +01:00 committed by GitHub
commit 3bf9ef0706
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
107 changed files with 2126 additions and 1843 deletions

View File

@ -58,7 +58,7 @@ jobs:
- name: Run cargo checks - name: Run cargo checks
run: | run: |
set -x 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 for VERSION in ${VERSIONS[@]}; do
echo "version=$VERSION" > config.txt echo "version=$VERSION" > config.txt
echo "suppress_build_script_link_lines=true" >> 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. fail-fast: false # If one platform fails, allow the rest to keep testing.
matrix: matrix:
rust: [stable] 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: platform:
[ [
{ {
@ -101,29 +101,14 @@ jobs:
}, },
] ]
exclude: exclude:
# PyPy 3.6 is EOL and not working on macos-latest (now macos-11) # PyPy doesn't release 32-bit Windows builds any more
- 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
- python-version: pypy-3.7 - python-version: pypy-3.7
platform: { os: "windows-latest", python-architecture: "x86" } platform: { os: "windows-latest", python-architecture: "x86" }
- python-version: pypy-3.8 - python-version: pypy-3.8
platform: { os: "windows-latest", python-architecture: "x86" } platform: { os: "windows-latest", python-architecture: "x86" }
include: 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 # Test minimal supported Rust version
- rust: 1.41.1 - rust: 1.48.0
python-version: "3.10" python-version: "3.10"
platform: platform:
{ {
@ -211,6 +196,10 @@ jobs:
- name: Build (all additive features) - name: Build (all additive features)
run: cargo build --lib --tests --no-default-features --features "${{ steps.settings.outputs.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). # Run tests (except on PyPy, because no embedding API).
- if: ${{ !startsWith(matrix.python-version, 'pypy') }} - if: ${{ !startsWith(matrix.python-version, 'pypy') }}
name: Test name: Test
@ -221,10 +210,10 @@ jobs:
name: Test (abi3) name: Test (abi3)
run: cargo test --no-default-features --features "abi3 ${{ steps.settings.outputs.all_additive_features }}" run: cargo test --no-default-features --features "abi3 ${{ steps.settings.outputs.all_additive_features }}"
# Run tests again, for abi3-py36 (the minimal Python version) # Run tests again, for abi3-py37 (the minimal Python version)
- if: ${{ (!startsWith(matrix.python-version, 'pypy')) && (matrix.python-version != '3.6') }} - if: ${{ (!startsWith(matrix.python-version, 'pypy')) && (matrix.python-version != '3.7') }}
name: Test (abi3-py36) name: Test (abi3-py37)
run: cargo test --no-default-features --features "abi3-py36 ${{ steps.settings.outputs.all_additive_features }}" run: cargo test --no-default-features --features "abi3-py37 ${{ steps.settings.outputs.all_additive_features }}"
- name: Test proc-macro code - name: Test proc-macro code
run: cargo test --manifest-path=pyo3-macros-backend/Cargo.toml run: cargo test --manifest-path=pyo3-macros-backend/Cargo.toml
@ -266,7 +255,11 @@ jobs:
coverage: coverage:
needs: [fmt] needs: [fmt]
runs-on: ubuntu-latest name: coverage-${{ matrix.os }}
strategy:
matrix:
os: ["windows", "macos", "ubuntu"]
runs-on: ${{ matrix.os }}-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-python@v2 - uses: actions/setup-python@v2
@ -280,12 +273,19 @@ jobs:
target target
key: coverage-cargo-${{ hashFiles('**/Cargo.toml') }} key: coverage-cargo-${{ hashFiles('**/Cargo.toml') }}
continue-on-error: true continue-on-error: true
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
profile: minimal
components: llvm-tools-preview
- name: install cargo-llvm-cov - name: install cargo-llvm-cov
shell: bash
run: | run: |
wget https://github.com/taiki-e/cargo-llvm-cov/releases/download/v${CARGO_LLVM_COV_VERSION}/cargo-llvm-cov-x86_64-unknown-linux-gnu.tar.gz -qO- | tar -xzvf - host=$(rustc -Vv | grep host | sed 's/host: //')
mv cargo-llvm-cov ~/.cargo/bin curl -fsSL https://github.com/taiki-e/cargo-llvm-cov/releases/download/v${CARGO_LLVM_COV_VERSION}/cargo-llvm-cov-"$host".tar.gz | tar xzf - -C ~/.cargo/bin
env: env:
CARGO_LLVM_COV_VERSION: 0.1.10 CARGO_LLVM_COV_VERSION: 0.1.11
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
toolchain: nightly toolchain: nightly
@ -299,8 +299,10 @@ jobs:
cargo llvm-cov --package $ALL_PACKAGES --no-report --features $(make list_all_additive_features) cargo llvm-cov --package $ALL_PACKAGES --no-report --features $(make list_all_additive_features)
cargo llvm-cov --package $ALL_PACKAGES --no-report --features abi3 $(make list_all_additive_features) cargo llvm-cov --package $ALL_PACKAGES --no-report --features abi3 $(make list_all_additive_features)
cargo llvm-cov --package $ALL_PACKAGES --no-run --lcov --output-path coverage.lcov cargo llvm-cov --package $ALL_PACKAGES --no-run --lcov --output-path coverage.lcov
shell: bash
env: env:
ALL_PACKAGES: pyo3 pyo3-build-config pyo3-macros-backend pyo3-macros ALL_PACKAGES: pyo3 pyo3-build-config pyo3-macros-backend pyo3-macros
- uses: codecov/codecov-action@v2 - uses: codecov/codecov-action@v2
with: with:
file: coverage.lcov file: coverage.lcov
name: ${{ matrix.os }}

View File

@ -28,8 +28,9 @@ To summarize, there are six main parts to the PyO3 codebase.
- [`src/class`] - [`src/class`]
5. [Procedural macros to simplify usage for users.](#5-procedural-macros-to-simplify-usage-for-users) 5. [Procedural macros to simplify usage for users.](#5-procedural-macros-to-simplify-usage-for-users)
- [`src/derive_utils.rs`], [`pyo3-macros`] and [`pyo3-macros-backend`] - [`src/derive_utils.rs`], [`pyo3-macros`] and [`pyo3-macros-backend`]
6. [`build.rs`](#6-buildrs) 6. [`build.rs` and `pyo3-build-config`](#6-buildrs-and-pyo3-build-config)
- [`build.rs`](https://github.com/PyO3/pyo3/tree/main/build.rs) - [`build.rs`](https://github.com/PyO3/pyo3/tree/main/build.rs)
- [`pyo3-build-config`]
## 1. Low-level bindings of Python/C API ## 1. Low-level bindings of Python/C API
@ -50,7 +51,7 @@ With `Py_LIMITED_API`, we can build a Python-version-agnostic binary called an
`Py_37` means that the API is available from Python >= 3.7. `Py_37` means that the API is available from Python >= 3.7.
There are also `Py_38`, `Py_39`, and so on. There are also `Py_38`, `Py_39`, and so on.
`PyPy` means that the API definition is for PyPy. `PyPy` means that the API definition is for PyPy.
Those flags are set in [`build.rs`](#6-buildrs). Those flags are set in [`build.rs`](#6-buildrs-and-pyo3-build-config).
## 2. Bindings to Python objects ## 2. Bindings to Python objects
@ -166,25 +167,37 @@ some internal tricks for making `#[pyproto]` flexible.
[`src/derive_utils.rs`] contains some utilities used in code generated by these proc-macros, [`src/derive_utils.rs`] contains some utilities used in code generated by these proc-macros,
such as parsing function arguments. such as parsing function arguments.
## 6. `build.rs` ## 6. `build.rs` and `pyo3-build-config`
PyO3's [`build.rs`](https://github.com/PyO3/pyo3/tree/main/build.rs) is relatively long PyO3 supports a wide range of OSes, interpreters and use cases. The correct environment must be
(about 900 lines) to support multiple architectures, interpreters, and usages. detected at build time in order to set up relevant conditional compilation correctly. This logic
Below is a non-exhaustive list of its functionality: is captured in the [`pyo3-build-config`] crate, which is a `build-dependency` of `pyo3` and
`pyo3-macros`, and can also be used by downstream users in the same way.
- Cross-compiling support. In [`pyo3-build-config`]'s `build.rs` the build environment is detected and inlined into the crate
- If `TARGET` architecture and `HOST` architecture differ, we find cross compile information as a "config file". This works in all cases except for cross-compiling, where it is necessary to
from environment variables (`PYO3_CROSS_LIB_DIR` and `PYO3_CROSS_PYTHON_VERSION`) or system files. capture this from the `pyo3` `build.rs` to get some extra environment variables that Cargo doesn't
set for build dependencies.
The `pyo3` `build.rs` also runs some safety checks such as ensuring the Python version detected is
actually supported.
Some of the functionality of `pyo3-build-config`:
- Find the interpreter for build and detect the Python version. - Find the interpreter for build and detect the Python version.
- We have to set some version flags like `Py_37`. - We have to set some version flags like `#[cfg(Py_3_7)]`.
- If the interpreter is PyPy, we set `PyPy`. - If the interpreter is PyPy, we set `#[cfg(PyPy)`.
- If `PYO3_NO_PYTHON` environment variable is set then the interpreter detection is bypassed - If the `PYO3_CONFIG_FILE` environment variable is set then that file's contents will be used
instead of any detected configuration.
- If the `PYO3_NO_PYTHON` environment variable is set then the interpreter detection is bypassed
entirely and only abi3 extensions can be built. entirely and only abi3 extensions can be built.
- Check if we are building a Python extension. - Check if we are building a Python extension.
- If we are building an extension (e.g., Python library installable by `pip`), - If we are building an extension (e.g., Python library installable by `pip`),
we don't link `libpython`. we don't link `libpython`.
Currently we use the `extension-module` feature for this purpose. This may change in the future. Currently we use the `extension-module` feature for this purpose. This may change in the future.
See [#1123](https://github.com/PyO3/pyo3/pull/1123). See [#1123](https://github.com/PyO3/pyo3/pull/1123).
- Cross-compiling configuration
- If `TARGET` architecture and `HOST` architecture differ, we find cross compile information
from environment variables (`PYO3_CROSS_LIB_DIR` and `PYO3_CROSS_PYTHON_VERSION`) or system files.
<!-- External Links --> <!-- External Links -->
@ -194,6 +207,7 @@ Below is a non-exhaustive list of its functionality:
[`pyo3-macros`]: https://github.com/PyO3/pyo3/tree/main/pyo3-macros [`pyo3-macros`]: https://github.com/PyO3/pyo3/tree/main/pyo3-macros
[`pyo3-macros-backend`]: https://github.com/PyO3/pyo3/tree/main/pyo3-macros-backend [`pyo3-macros-backend`]: https://github.com/PyO3/pyo3/tree/main/pyo3-macros-backend
[`pyo3-build-config`]: https://github.com/PyO3/pyo3/tree/main/pyo3-build-config
<!-- Directories --> <!-- Directories -->

View File

@ -6,6 +6,48 @@ 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/) 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 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)
### Added
- Add `Py::setattr` method. [#2009](https://github.com/PyO3/pyo3/pull/2009)
### 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)
### Removed
- Remove `PyType::is_instance`, which is unintuitive; instead of `typ.is_instance(obj)`, use `obj.is_instance(typ)`. [#1985](https://github.com/PyO3/pyo3/pull/1985)
- Remove all functionality deprecated in PyO3 0.14. [#2007](https://github.com/PyO3/pyo3/pull/2007)
## [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
- `#[classattr]` constants with a known magic method name (which is lowercase) no longer trigger lint warnings expecting constants to be uppercase. [#1969](https://github.com/PyO3/pyo3/pull/1969)
### 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 ## [0.15.0] - 2021-11-03
### Packaging ### Packaging
@ -968,7 +1010,8 @@ Yanked
- Initial release - 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.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.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 [0.14.4]: https://github.com/pyo3/pyo3/compare/v0.14.3...v0.14.4

View File

@ -1,6 +1,6 @@
[package] [package]
name = "pyo3" name = "pyo3"
version = "0.15.0" version = "0.15.1"
description = "Bindings to Python interpreter" description = "Bindings to Python interpreter"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"] authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
readme = "README.md" readme = "README.md"
@ -20,10 +20,9 @@ libc = "0.2.62"
parking_lot = "0.11.0" parking_lot = "0.11.0"
# support crates for macros feature # support crates for macros feature
pyo3-macros = { path = "pyo3-macros", version = "=0.15.0", optional = true } pyo3-macros = { path = "pyo3-macros", version = "=0.15.1", optional = true }
# indoc must stay at 0.3.x for Rust 1.41 compatibility indoc = { version = "1.0.3", optional = true }
indoc = { version = "0.3.6", optional = true } paste = { version = "1.0.6", optional = true }
paste = { version = "0.1.18", optional = true }
unindent = { version = "0.1.4", optional = true } unindent = { version = "0.1.4", optional = true }
# support crate for multiple-pymethods feature # support crate for multiple-pymethods feature
@ -32,7 +31,7 @@ inventory = { version = "0.1.4", optional = true }
# crate integrations that can be added using the eponymous features # crate integrations that can be added using the eponymous features
anyhow = { version = "1.0", optional = true } 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 } hashbrown = { version = ">= 0.9, < 0.12", optional = true }
indexmap = { version = ">= 1.6, < 1.8", optional = true } indexmap = { version = ">= 1.6, < 1.8", optional = true }
num-bigint = { version = "0.4", optional = true } num-bigint = { version = "0.4", optional = true }
@ -41,11 +40,7 @@ serde = { version = "1.0", optional = true }
[dev-dependencies] [dev-dependencies]
assert_approx_eq = "1.1.0" assert_approx_eq = "1.1.0"
# O.3.5 uses the matches! macro, which isn't compatible with Rust 1.41 criterion = "0.3.5"
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"
trybuild = "1.0.49" trybuild = "1.0.49"
rustversion = "1.0" rustversion = "1.0"
# 1.0.0 requires Rust 1.50 # 1.0.0 requires Rust 1.50
@ -56,7 +51,7 @@ serde_json = "1.0.61"
pyo3 = { path = ".", default-features = false, features = ["macros", "auto-initialize"] } pyo3 = { path = ".", default-features = false, features = ["macros", "auto-initialize"] }
[build-dependencies] [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] [features]
default = ["macros"] default = ["macros"]
@ -76,7 +71,6 @@ extension-module = []
abi3 = ["pyo3-build-config/abi3"] abi3 = ["pyo3-build-config/abi3"]
# With abi3, we can manually set the minimum Python version. # 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-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37"]
abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38"] abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38"]
abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39"] abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39"]

View File

@ -4,7 +4,7 @@
[![benchmark](https://github.com/PyO3/pyo3/actions/workflows/bench.yml/badge.svg)](https://pyo3.rs/dev/bench/) [![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) [![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) [![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) [![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) [![contributing notes](https://img.shields.io/badge/contribute-on%20github-Green)](https://github.com/PyO3/pyo3/blob/main/Contributing.md)
@ -17,8 +17,8 @@
## Usage ## Usage
PyO3 supports the following software versions: PyO3 supports the following software versions:
- Python 3.6 and up (CPython and PyPy) - Python 3.7 and up (CPython and PyPy)
- Rust 1.41 and up - 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. 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.
@ -46,7 +46,7 @@ name = "string_sum"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies.pyo3] [dependencies.pyo3]
version = "0.15.0" version = "0.15.1"
features = ["extension-module"] features = ["extension-module"]
``` ```
@ -108,7 +108,7 @@ Start a new project with `cargo new` and add `pyo3` to the `Cargo.toml` like th
```toml ```toml
[dependencies.pyo3] [dependencies.pyo3]
version = "0.15.0" version = "0.15.1"
features = ["auto-initialize"] features = ["auto-initialize"]
``` ```
@ -171,6 +171,7 @@ about this topic.
## Articles and other media ## 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 - [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 - [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 - [Vortexa - Integrating Rust into Python](https://www.vortexa.com/insight/integrating-rust-into-python) - Apr 12, 2021

View File

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

View File

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

View File

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

View File

@ -18,11 +18,13 @@ PYO3_VERSION_TAG = os.environ.get("PYO3_VERSION_TAG", "main")
if PYO3_VERSION_TAG == "main": if PYO3_VERSION_TAG == "main":
PYO3_DOCS_URL = "https://pyo3.rs/main/doc" PYO3_DOCS_URL = "https://pyo3.rs/main/doc"
PYO3_DOCS_VERSION = "latest"
PYO3_CRATE_VERSION = 'git = "https://github.com/pyo3/pyo3"' PYO3_CRATE_VERSION = 'git = "https://github.com/pyo3/pyo3"'
else: else:
# v0.13.2 -> 0.13.2 # v0.13.2 -> 0.13.2
version = PYO3_VERSION_TAG.lstrip("v") version = PYO3_VERSION_TAG.lstrip("v")
PYO3_DOCS_URL = f"https://docs.rs/pyo3/{version}" PYO3_DOCS_URL = f"https://docs.rs/pyo3/{version}"
PYO3_DOCS_VERSION = version
PYO3_CRATE_VERSION = f'version = "{version}"' PYO3_CRATE_VERSION = f'version = "{version}"'
@ -35,6 +37,7 @@ def replace_section_content(section):
section["Chapter"]["content"] section["Chapter"]["content"]
.replace("{{#PYO3_VERSION_TAG}}", PYO3_VERSION_TAG) .replace("{{#PYO3_VERSION_TAG}}", PYO3_VERSION_TAG)
.replace("{{#PYO3_DOCS_URL}}", PYO3_DOCS_URL) .replace("{{#PYO3_DOCS_URL}}", PYO3_DOCS_URL)
.replace("{{#PYO3_DOCS_VERSION}}", PYO3_DOCS_VERSION)
.replace("{{#PYO3_CRATE_VERSION}}", PYO3_CRATE_VERSION) .replace("{{#PYO3_CRATE_VERSION}}", PYO3_CRATE_VERSION)
) )

View File

@ -31,4 +31,5 @@
[Appendix A: Migration Guide](migration.md) [Appendix A: Migration Guide](migration.md)
[Appendix B: PyO3 and rust-cpython](rust_cpython.md) [Appendix B: PyO3 and rust-cpython](rust_cpython.md)
[Appendix C: Trait bounds](trait_bounds.md) [Appendix C: Trait bounds](trait_bounds.md)
[Appendix D: Python typing hints](python_typing_hints.md)
[CHANGELOG](changelog.md) [CHANGELOG](changelog.md)

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 `python` executable (if it's a Python 3 interpreter).
- The `python3` executable. - 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. 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.
@ -44,7 +44,11 @@ Caused by:
build_flags=WITH_THREAD build_flags=WITH_THREAD
``` ```
> Note: if you save the output config to a file, it is possible to manually override the contents and feed it back into PyO3 using the `PYO3_CONFIG_FILE` env var. For now, this is an advanced feature that should not be needed for most users. The format of the config file and its contents are deliberately unstable and undocumented. If you have a production use-case for this config file, please file an issue and help us stabilize it! ### Advanced: config files
If you save the above output config from `PYO3_PRINT_CONFIG` to a file, it is possible to manually override the contents and feed it back into PyO3 using the `PYO3_CONFIG_FILE` env var.
If your build environment is unusual enough that PyO3's regular configuration detection doesn't work, using a config file like this will give you the flexibility to make PyO3 work for you. To see the full set of options supported, see the documentation for the [`InterpreterConfig` struct](https://docs.rs/pyo3-build-config/{{#PYO3_DOCS_VERSION}}/pyo3_build_config/struct.InterpreterConfig.html).
## Building Python extension modules ## Building Python extension modules
@ -141,16 +145,16 @@ See the [corresponding](https://github.com/PyO3/maturin/pull/353) [PRs](https://
#### Minimum Python version for `abi3` #### 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. 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-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`. 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. 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`. 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 #### Missing features

View File

@ -696,6 +696,112 @@ num=44
num=-1 num=-1
``` ```
## Making class method signatures available to Python
The [`#[pyo3(text_signature = "...")]`](./function.md#text_signature) option for `#[pyfunction]` also works for classes and methods:
```rust
# #![allow(dead_code)]
use pyo3::prelude::*;
use pyo3::types::PyType;
// it works even if the item is not documented:
#[pyclass]
#[pyo3(text_signature = "(c, d, /)")]
struct MyClass {}
#[pymethods]
impl MyClass {
// the signature for the constructor is attached
// to the struct definition instead.
#[new]
fn new(c: i32, d: &str) -> Self {
Self {}
}
// the self argument should be written $self
#[pyo3(text_signature = "($self, e, f)")]
fn my_method(&self, e: i32, f: i32) -> i32 {
e + f
}
#[classmethod]
#[pyo3(text_signature = "(cls, e, f)")]
fn my_class_method(cls: &PyType, e: i32, f: i32) -> i32 {
e + f
}
#[staticmethod]
#[pyo3(text_signature = "(e, f)")]
fn my_static_method(e: i32, f: i32) -> i32 {
e + f
}
}
#
# fn main() -> PyResult<()> {
# Python::with_gil(|py| {
# let inspect = PyModule::import(py, "inspect")?.getattr("signature")?;
# let module = PyModule::new(py, "my_module")?;
# module.add_class::<MyClass>()?;
# let class = module.getattr("MyClass")?;
#
# if cfg!(not(Py_LIMITED_API)) || py.version_info() >= (3, 10) {
# let doc: String = class.getattr("__doc__")?.extract()?;
# assert_eq!(doc, "");
#
# let sig: String = inspect
# .call1((class,))?
# .call_method0("__str__")?
# .extract()?;
# assert_eq!(sig, "(c, d, /)");
# } else {
# let doc: String = class.getattr("__doc__")?.extract()?;
# assert_eq!(doc, "");
#
# inspect.call1((class,)).expect_err("`text_signature` on classes is not compatible with compilation in `abi3` mode until Python 3.10 or greater");
# }
#
# {
# let method = class.getattr("my_method")?;
#
# assert!(method.getattr("__doc__")?.is_none());
#
# let sig: String = inspect
# .call1((method,))?
# .call_method0("__str__")?
# .extract()?;
# assert_eq!(sig, "(self, /, e, f)");
# }
#
# {
# let method = class.getattr("my_class_method")?;
#
# assert!(method.getattr("__doc__")?.is_none());
#
# let sig: String = inspect
# .call1((method,))?
# .call_method0("__str__")?
# .extract()?;
# assert_eq!(sig, "(cls, e, f)");
# }
#
# {
# let method = class.getattr("my_static_method")?;
#
# assert!(method.getattr("__doc__")?.is_none());
#
# let sig: String = inspect
# .call1((method,))?
# .call_method0("__str__")?
# .extract()?;
# assert_eq!(sig, "(e, f)");
# }
#
# Ok(())
# })
# }
```
Note that `text_signature` on classes is not compatible with compilation in
`abi3` mode until Python 3.10 or greater.
## Implementation details ## Implementation details
The `#[pyclass]` macros rely on a lot of conditional code generation: each `#[pyclass]` can optionally have a `#[pymethods]` block as well as several different possible `#[pyproto]` trait implementations. The `#[pyclass]` macros rely on a lot of conditional code generation: each `#[pyclass]` can optionally have a `#[pymethods]` block as well as several different possible `#[pyproto]` trait implementations.
@ -765,7 +871,6 @@ impl ::pyo3::class::impl_::PyClassImpl for MyClass {
visitor(collector.py_class_descriptors()); visitor(collector.py_class_descriptors());
visitor(collector.object_protocol_methods()); visitor(collector.object_protocol_methods());
visitor(collector.async_protocol_methods()); visitor(collector.async_protocol_methods());
visitor(collector.context_protocol_methods());
visitor(collector.descr_protocol_methods()); visitor(collector.descr_protocol_methods());
visitor(collector.mapping_protocol_methods()); visitor(collector.mapping_protocol_methods());
visitor(collector.number_protocol_methods()); visitor(collector.number_protocol_methods());

View File

@ -47,6 +47,25 @@ given signatures should be interpreted as follows:
- `__str__(<self>) -> object (str)` - `__str__(<self>) -> object (str)`
- `__repr__(<self>) -> object (str)` - `__repr__(<self>) -> object (str)`
- `__hash__(<self>) -> isize` - `__hash__(<self>) -> isize`
<details>
<summary>Disabling Python's default hash</summary>
By default, all `#[pyclass]` types have a default hash implementation from Python. Types which should not be hashable can override this by setting `__hash__` to `None`. This is the same mechanism as for a pure-Python class. This is done like so:
```rust
# use pyo3::prelude::*;
#
#[pyclass]
struct NotHashable { }
#[pymethods]
impl NotHashable {
#[classattr]
const __hash__: Option<PyObject> = None;
}
```
</details>
- `__richcmp__(<self>, object, pyo3::basic::CompareOp) -> object` - `__richcmp__(<self>, object, pyo3::basic::CompareOp) -> object`
- `__getattr__(<self>, object) -> object` - `__getattr__(<self>, object) -> object`
- `__setattr__(<self>, object, object) -> ()` - `__setattr__(<self>, object, object) -> ()`
@ -79,7 +98,7 @@ impl PyCounter {
fn __new__(wraps: Py<PyAny>) -> Self { fn __new__(wraps: Py<PyAny>) -> Self {
PyCounter { count: 0, wraps } PyCounter { count: 0, wraps }
} }
#[args(args="*", kwargs="**")]
fn __call__( fn __call__(
&mut self, &mut self,
py: Python, py: Python,
@ -140,6 +159,24 @@ TODO; see [#1884](https://github.com/PyO3/pyo3/issues/1884)
- `__len__(<self>) -> usize` - `__len__(<self>) -> usize`
- `__contains__(<self>, object) -> bool` - `__contains__(<self>, object) -> bool`
<details>
<summary>Disabling Python's default contains</summary>
By default, all `#[pyclass]` types with an `__iter__` method support a default implementation of the `in` operator. Types which do not want this can override this by setting `__contains__` to `None`. This is the same mechanism as for a pure-Python class. This is done like so:
```rust
# use pyo3::prelude::*;
#
#[pyclass]
struct NoContains { }
#[pymethods]
impl NoContains {
#[classattr]
const __contains__: Option<PyObject> = None;
}
```
</details>
- `__getitem__(<self>, object) -> object` - `__getitem__(<self>, object) -> object`
- `__setitem__(<self>, object, object) -> ()` - `__setitem__(<self>, object, object) -> ()`
- `__delitem__(<self>, object) -> ()` - `__delitem__(<self>, object) -> ()`

View File

@ -17,7 +17,7 @@ variety of Rust types, which you can check out in the implementor list of
[`FromPyObject`]. [`FromPyObject`].
[`FromPyObject`] is also implemented for your own Rust types wrapped as Python [`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 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`] mutable references, you have to extract the PyO3 reference wrappers [`PyRef`]
and [`PyRefMut`]. They work like the reference wrappers of and [`PyRefMut`]. They work like the reference wrappers of
@ -413,7 +413,7 @@ enum RustyEnum {
# { # {
# let thing = b"foo".to_object(py); # let thing = b"foo".to_object(py);
# let error = thing.extract::<RustyEnum>(py).unwrap_err(); # 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(()) # 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. 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). [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 { pyo3_asyncio::async_std::run(py, async move {
// verify that we are on a uvloop.Loop // verify that we are on a uvloop.Loop
Python::with_gil(|py| -> PyResult<()> { Python::with_gil(|py| -> PyResult<()> {
assert!(uvloop assert!(pyo3_asyncio::async_std::get_current_loop(py)?.is_instance(
uvloop
.as_ref(py) .as_ref(py)
.getattr("Loop")? .getattr("Loop")?
.downcast::<PyType>() .downcast::<PyType>()
.unwrap() .unwrap()
.is_instance(pyo3_asyncio::async_std::get_current_loop(py)?)?); )?);
Ok(()) Ok(())
})?; })?;

View File

@ -100,21 +100,19 @@ PyErr::from_instance(py, err).restore(py);
## Checking exception types ## Checking exception types
Python has an [`isinstance`](https://docs.python.org/3/library/functions.html#isinstance) method to check an object's type. 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 ```rust
use pyo3::Python; use pyo3::Python;
use pyo3::types::{PyBool, PyList}; use pyo3::types::{PyBool, PyList};
Python::with_gil(|py| { 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]); let list = PyList::new(py, &[1, 2, 3, 4]);
assert!(!list.is_instance::<PyBool>().unwrap()); assert!(!list.is_instance_of::<PyBool>().unwrap());
assert!(list.is_instance::<PyList>().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: 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::*; # use pyo3::prelude::*;
# Python::with_gil(|py| { # Python::with_gil(|py| {
# let err = PyTypeError::new_err(()); # let err = PyTypeError::new_err(());
err.is_instance::<PyTypeError>(py); err.is_instance_of::<PyTypeError>(py);
# }); # });
``` ```
@ -184,7 +182,7 @@ fn main() {
Python::with_gil(|py| { Python::with_gil(|py| {
let fun = pyo3::wrap_pyfunction!(connect, py).unwrap(); let fun = pyo3::wrap_pyfunction!(connect, py).unwrap();
let err = fun.call1(("0.0.0.0",)).unwrap_err(); let err = fun.call1(("0.0.0.0",)).unwrap_err();
assert!(err.is_instance::<PyOSError>(py)); assert!(err.is_instance_of::<PyOSError>(py));
}); });
} }
``` ```
@ -209,13 +207,13 @@ fn parse_int(s: String) -> PyResult<usize> {
# #
# assert!(parse_int(String::from("-1")) # assert!(parse_int(String::from("-1"))
# .unwrap_err() # .unwrap_err()
# .is_instance::<PyValueError>(py)); # .is_instance_of::<PyValueError>(py));
# assert!(parse_int(String::from("foo")) # assert!(parse_int(String::from("foo"))
# .unwrap_err() # .unwrap_err()
# .is_instance::<PyValueError>(py)); # .is_instance_of::<PyValueError>(py));
# assert!(parse_int(String::from("13.37")) # assert!(parse_int(String::from("13.37"))
# .unwrap_err() # .unwrap_err()
# .is_instance::<PyValueError>(py)); # .is_instance_of::<PyValueError>(py));
# }) # })
# } # }
``` ```
@ -257,5 +255,5 @@ defines exceptions for several standard library modules.
[`PyErr`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html [`PyErr`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html
[`PyResult`]: {{#PYO3_DOCS_URL}}/pyo3/type.PyResult.html [`PyResult`]: {{#PYO3_DOCS_URL}}/pyo3/type.PyResult.html
[`PyErr::from_instance`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html#method.from_instance [`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
[`PyAny::is_instance`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#method.is_instance [`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

@ -24,7 +24,7 @@ See the [building and distribution](building_and_distribution.md#py_limited_apia
### The `abi3-pyXY` features ### 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. 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` ### `resolve-config`
The `resolve-config` feature of the `pyo3-build-config` crate controls whether that crate's 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 build script automatically resolves a Python interpreter / build configuration. This feature is primarily useful when building PyO3
this feature enables this crate to be used in *library mode*. This may be desirable for 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.
use cases where you want to read or write PyO3 build configuration files or resolve
metadata about a Python interpreter.
## Optional Dependencies ## Optional Dependencies
@ -127,5 +125,3 @@ struct User {
permissions: Vec<Py<Permission>> permissions: Vec<Py<Permission>>
} }
``` ```

View File

@ -1,8 +1,8 @@
# Python Functions # Python Functions
PyO3 supports two ways to define a free function in Python. Both require registering the function to a [module](./module.md). The `#[pyfunction]` attribute is used to define a Python function from a Rust function. Once defined, the function needs to be added to a [module](./module.md) using the `wrap_pyfunction!` macro.
One way is annotating a function with `#[pyfunction]` and then adding it to the module using the `wrap_pyfunction!` macro. The following example defines a function called `double` in a Python module called `my_extension`:
```rust ```rust
use pyo3::prelude::*; use pyo3::prelude::*;
@ -19,50 +19,27 @@ fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
} }
``` ```
Alternatively, there is a shorthand: the function can be placed inside the module definition and This chapter of the guide explains full usage of the `#[pyfunction]` attribute. The following topics are covered:
annotated with `#[pyfn]`, as below:
```rust - [Function options](#function-options)
use pyo3::prelude::*; - [`#[pyo3(name = "...")]`](#name)
- [`#[pyo3(text_signature = "...")]`](#text_signature)
#[pymodule] - [`#[pyo3(pass_module)]`](#pass_module)
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> { - [Argument parsing](#argument-parsing)
- [`#[pyo3(from_py_with = "...")]`](#from_py_with)
#[pyfn(m)] - [Advanced function patterns](#advanced-function-patterns)
fn double(x: usize) -> usize { - [`#[pyfn]` shorthand](#pyfn-shorthand)
x * 2
}
Ok(())
}
```
`#[pyfn(m)]` is just syntactic sugar for `#[pyfunction]`, and takes all the same options
documented in the rest of this chapter. The code above is expanded to the following:
```rust
use pyo3::prelude::*;
#[pymodule]
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
#[pyfunction]
fn double(x: usize) -> usize {
x * 2
}
m.add_function(wrap_pyfunction!(double, m)?)?;
Ok(())
}
```
## Function options ## Function options
The `#[pyo3]` attribute can be used to modify properties of the generated Python function. It can take any combination of the following options: The `#[pyo3]` attribute can be used to modify properties of the generated Python function. It can take any combination of the following options:
- `#[pyo3(name = "...")]` - <a name="name"></a> `#[pyo3(name = "...")]`
Overrides the name generated in Python: Overrides the name exposed to Python.
In the following example, the Rust function `no_args_py` will be added to the Python module
`module_with_functions` as the Python function `no_args`:
```rust ```rust
use pyo3::prelude::*; use pyo3::prelude::*;
@ -84,6 +61,63 @@ The `#[pyo3]` attribute can be used to modify properties of the generated Python
# }); # });
``` ```
- <a name="text_signature"></a> `#[pyo3(text_signature = "...")]`
Sets the function signature visible in Python tooling (such as via [`inspect.signature`]).
The example below creates a function `add` which has a signature describing two positional-only
arguments `a` and `b`.
```rust
use pyo3::prelude::*;
/// This function adds two unsigned 64-bit integers.
#[pyfunction]
#[pyo3(text_signature = "(a, b, /)")]
fn add(a: u64, b: u64) -> u64 {
a + b
}
#
# fn main() -> PyResult<()> {
# Python::with_gil(|py| {
# let fun = pyo3::wrap_pyfunction!(add, py)?;
#
# let doc: String = fun.getattr("__doc__")?.extract()?;
# assert_eq!(doc, "This function adds two unsigned 64-bit integers.");
#
# let inspect = PyModule::import(py, "inspect")?.getattr("signature")?;
# let sig: String = inspect
# .call1((fun,))?
# .call_method0("__str__")?
# .extract()?;
# assert_eq!(sig, "(a, b, /)");
#
# Ok(())
# })
# }
```
- <a name="pass_module" ></a> `#[pyo3(pass_module)]`
Set this option to make PyO3 pass the containing module as the first argument to the function. It is then possible to use the module in the function body. The first argument **must** be of type `&PyModule`.
The following example creates a function `pyfunction_with_module` which returns the containing module's name (i.e. `module_with_fn`):
```rust
use pyo3::prelude::*;
#[pyfunction]
#[pyo3(pass_module)]
fn pyfunction_with_module(module: &PyModule) -> PyResult<&str> {
module.name()
}
#[pymodule]
fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(pyfunction_with_module, m)?)
}
```
## Argument parsing ## Argument parsing
The `#[pyfunction]` attribute supports specifying details of argument parsing. The details are given in the section ["Method arguments" of the Classes chapter](class.md#method-arguments). Here is an example for a function that accepts arbitrary keyword arguments (`**kwargs` in Python syntax) and returns the number that was passed: The `#[pyfunction]` attribute supports specifying details of argument parsing. The details are given in the section ["Method arguments" of the Classes chapter](class.md#method-arguments). Here is an example for a function that accepts arbitrary keyword arguments (`**kwargs` in Python syntax) and returns the number that was passed:
@ -104,146 +138,38 @@ fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
} }
``` ```
## Making the function signature available to Python ### Per-argument options
In order to make the function signature available to Python to be retrieved via The `#[pyo3]` attribute can be used on individual arguments to modify properties of them in the generated function. It can take any combination of the following options:
`inspect.signature`, use the `#[pyo3(text_signature)]` annotation as in the example
below. The `/` signifies the end of positional-only arguments. (This
is not a feature of this library in particular, but the general format used by
CPython for annotating signatures of built-in functions.)
```rust - <a name="from_py_with"></a> `#[pyo3(from_py_with = "...")]`
use pyo3::prelude::*;
/// This function adds two unsigned 64-bit integers. Set this on an option to specify a custom function to convert the function argument from Python to the desired Rust type, instead of using the default `FromPyObject` extraction. The function signature must be `fn(&PyAny) -> PyResult<T>` where `T` is the Rust type of the argument.
#[pyfunction]
#[pyo3(text_signature = "(a, b, /)")]
fn add(a: u64, b: u64) -> u64 {
a + b
}
#
# fn main() -> PyResult<()> {
# Python::with_gil(|py| {
# let fun = pyo3::wrap_pyfunction!(add, py)?;
#
# let doc: String = fun.getattr("__doc__")?.extract()?;
# assert_eq!(doc, "This function adds two unsigned 64-bit integers.");
#
# let inspect = PyModule::import(py, "inspect")?.getattr("signature")?;
# let sig: String = inspect
# .call1((fun,))?
# .call_method0("__str__")?
# .extract()?;
# assert_eq!(sig, "(a, b, /)");
#
# Ok(())
# })
# }
```
This also works for classes and methods: The following example uses `from_py_with` to convert the input Python object to its length:
```rust ```rust
# #![allow(dead_code)] use pyo3::prelude::*;
use pyo3::prelude::*;
use pyo3::types::PyType;
// it works even if the item is not documented: fn get_length(obj: &PyAny) -> PyResult<usize> {
#[pyclass] let length = obj.len()?;
#[pyo3(text_signature = "(c, d, /)")] Ok(length)
struct MyClass {}
#[pymethods]
impl MyClass {
// the signature for the constructor is attached
// to the struct definition instead.
#[new]
fn new(c: i32, d: &str) -> Self {
Self {}
} }
// the self argument should be written $self
#[pyo3(text_signature = "($self, e, f)")]
fn my_method(&self, e: i32, f: i32) -> i32 {
e + f
}
#[classmethod]
#[pyo3(text_signature = "(cls, e, f)")]
fn my_class_method(cls: &PyType, e: i32, f: i32) -> i32 {
e + f
}
#[staticmethod]
#[pyo3(text_signature = "(e, f)")]
fn my_static_method(e: i32, f: i32) -> i32 {
e + f
}
}
#
# fn main() -> PyResult<()> {
# Python::with_gil(|py| {
# let inspect = PyModule::import(py, "inspect")?.getattr("signature")?;
# let module = PyModule::new(py, "my_module")?;
# module.add_class::<MyClass>()?;
# let class = module.getattr("MyClass")?;
#
# if cfg!(not(Py_LIMITED_API)) || py.version_info() >= (3, 10) {
# let doc: String = class.getattr("__doc__")?.extract()?;
# assert_eq!(doc, "");
#
# let sig: String = inspect
# .call1((class,))?
# .call_method0("__str__")?
# .extract()?;
# assert_eq!(sig, "(c, d, /)");
# } else {
# let doc: String = class.getattr("__doc__")?.extract()?;
# assert_eq!(doc, "");
#
# inspect.call1((class,)).expect_err("`text_signature` on classes is not compatible with compilation in `abi3` mode until Python 3.10 or greater");
# }
#
# {
# let method = class.getattr("my_method")?;
#
# assert!(method.getattr("__doc__")?.is_none());
#
# let sig: String = inspect
# .call1((method,))?
# .call_method0("__str__")?
# .extract()?;
# assert_eq!(sig, "(self, /, e, f)");
# }
#
# {
# let method = class.getattr("my_class_method")?;
#
# assert!(method.getattr("__doc__")?.is_none());
#
# let sig: String = inspect
# .call1((method,))?
# .call_method0("__str__")?
# .extract()?;
# assert_eq!(sig, "(cls, e, f)");
# }
#
# {
# let method = class.getattr("my_static_method")?;
#
# assert!(method.getattr("__doc__")?.is_none());
#
# let sig: String = inspect
# .call1((method,))?
# .call_method0("__str__")?
# .extract()?;
# assert_eq!(sig, "(e, f)");
# }
#
# Ok(())
# })
# }
```
Note that `text_signature` on classes is not compatible with compilation in #[pyfunction]
`abi3` mode until Python 3.10 or greater. fn object_length(
#[pyo3(from_py_with = "get_length")] argument: usize
) -> usize {
argument
}
# Python::with_gil(|py| {
# let f = pyo3::wrap_pyfunction!(object_length)(py).unwrap();
# assert_eq!(f.call1((vec![1, 2, 3],)).unwrap().extract::<usize>().unwrap(), 3);
# });
```
## Advanced function patterns
### Making the function signature available to Python (old method) ### Making the function signature available to Python (old method)
@ -288,7 +214,7 @@ Docstring: This function adds two unsigned 64-bit integers.
Type: builtin_function_or_method Type: builtin_function_or_method
``` ```
## Closures ### Closures
Currently, there are no conversions between `Fn`s in Rust and callables in Python. This would Currently, there are no conversions between `Fn`s in Rust and callables in Python. This would
definitely be possible and very useful, so contributions are welcome. In the meantime, you can do definitely be possible and very useful, so contributions are welcome. In the meantime, you can do
@ -325,29 +251,7 @@ in Python code.
[`PyFunction`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyFunction.html [`PyFunction`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyFunction.html
[`PyCFunction`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyCFunction.html [`PyCFunction`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyCFunction.html
### Accessing the module of a function ### Accessing the FFI functions
It is possible to access the module of a `#[pyfunction]` in the function body by using `#[pyo3(pass_module)]` option:
```rust
use pyo3::prelude::*;
#[pyfunction]
#[pyo3(pass_module)]
fn pyfunction_with_module(module: &PyModule) -> PyResult<&str> {
module.name()
}
#[pymodule]
fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(pyfunction_with_module, m)?)
}
```
If `pass_module` is set, the first argument **must** be the `&PyModule`. It is then possible to use the module
in the function body.
## Accessing the FFI functions
In order to make Rust functions callable from Python, PyO3 generates an `extern "C"` In order to make Rust functions callable from Python, PyO3 generates an `extern "C"`
function whose exact signature depends on the Rust signature. (PyO3 chooses the optimal function whose exact signature depends on the Rust signature. (PyO3 chooses the optimal
@ -357,3 +261,46 @@ arguments from the input `PyObject`s.
The `wrap_pyfunction` macro can be used to directly get a `PyCFunction` given a The `wrap_pyfunction` macro can be used to directly get a `PyCFunction` given a
`#[pyfunction]` and a `PyModule`: `wrap_pyfunction!(rust_fun, module)`. `#[pyfunction]` and a `PyModule`: `wrap_pyfunction!(rust_fun, module)`.
## `#[pyfn]` shorthand
There is a shorthand to `#[pyfunction]` and `wrap_pymodule!`: the function can be placed inside the module definition and
annotated with `#[pyfn]`. To simplify PyO3, it is expected that `#[pyfn]` may be removed in a future release (See [#694](https://github.com/PyO3/pyo3/issues/694)).
An example of `#[pyfn]` is below:
```rust
use pyo3::prelude::*;
#[pymodule]
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
#[pyfn(m)]
fn double(x: usize) -> usize {
x * 2
}
Ok(())
}
```
`#[pyfn(m)]` is just syntactic sugar for `#[pyfunction]`, and takes all the same options
documented in the rest of this chapter. The code above is expanded to the following:
```rust
use pyo3::prelude::*;
#[pymodule]
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
#[pyfunction]
fn double(x: usize) -> usize {
x * 2
}
m.add_function(wrap_pyfunction!(double, m)?)?;
Ok(())
}
```
[`inspect.signature`]: https://docs.python.org/3/library/inspect.html#inspect.signature

View File

@ -3,6 +3,12 @@
This guide can help you upgrade code through breaking changes from one PyO3 version to the next. 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). 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 ## from 0.14.* to 0.15
### Changes in sequence indexing ### Changes in sequence indexing

View File

@ -0,0 +1,163 @@
# Typing and IDE hints for you Python package
PyO3 provides an easy to use interface to code native Python libraries in Rust. The accompanying Maturin allows you to build and publish them as a package. Yet, for the better user experience, Python libraries should provide typing hints and documentation for all public entities, so that IDEs can show them during development and type analyzing tools such as `mypy` can use them to properly verify the code.
Currently the best solution for the problem is to maintain manually the `*.pyi` files and ship them along with the package.
## The `pyi` files introduction
`pyi` (an abbreviation for `Python Interface`) is called a `Stub File` in most of the documentations related to them. Very good definition of what it is can be found in [old MyPy documentation](https://github.com/python/mypy/wiki/Creating-Stubs-For-Python-Modules):
> A stubs file only contains a description of the public interface of the module without any implementations.
Probably most Python developers encountered them already when trying to use the IDE "Go to Definition" function on any builtin type. For example the definitions of few standard exceptions look like this:
```python
class BaseException(object):
args: Tuple[Any, ...]
__cause__: BaseException | None
__context__: BaseException | None
__suppress_context__: bool
__traceback__: TracebackType | None
def __init__(self, *args: object) -> None: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
def with_traceback(self: _TBE, tb: TracebackType | None) -> _TBE: ...
class SystemExit(BaseException):
code: int
class Exception(BaseException): ...
class StopIteration(Exception):
value: Any
```
As we can see those are not full definitions containing implementation, but just a description of interface. It is usually all that is needed by the user of the library.
### What does the PEPs say?
As of the time of writing this documentation the `pyi` files are referenced in three PEPs.
[PEP8 - Style Guide for Python Code - #Function Annotations](https://www.python.org/dev/peps/pep-0008/#function-annotations) (last point) recommends all third party library creators to provide stub files as the source of knowledge about the package for type checker tools.
> (...) it is expected that users of third party library packages may want to run type checkers over those packages. For this purpose [PEP 484](https://www.python.org/dev/peps/pep-0484) recommends the use of stub files: .pyi files that are read by the type checker in preference of the corresponding .py files. (...)
[PEP484 - Type Hints - #Stub Files](https://www.python.org/dev/peps/pep-0484/#stub-files) defines stub files as follows.
> Stub files are files containing type hints that are only for use by the type checker, not at runtime.
It contains a specification for them (highly recommended reading, since it contains at least one thing that is not used in normal Python code) and also some general information about where to store the stub files.
[PEP561 - Distributing and Packaging Type Information](https://www.python.org/dev/peps/pep-0561/) describes in detail how to build packages that will enable type checking. In particular it contains information about how the stub files must be distributed in order for type checkers to use them.
## How to do it?
[PEP561](https://www.python.org/dev/peps/pep-0561/) recognizes three ways of distributing type information:
* `inline` - the typing is placed directly in source (`py`) files;
* `separate package with stub files` - the typing is placed in `pyi` files distributed in their own, separate package;
* `in-package stub files` - the typing is placed in `pyi` files distributed in the same package as source files.
The first way is tricky with PyO3 since we do not have `py` files. When it will be investigated and necessary changes are implemented, this document will be updated.
The second way is easy to do, and the whole work can be fully separated from the main library code. The example repo for the package with stub files can be found in [PEP561 references section](https://www.python.org/dev/peps/pep-0561/#references): [Stub package repository](https://github.com/ethanhs/stub-package)
The third way is described below.
### Including `pyi` files in your PyO3/Maturin build package
When source files are in the same package as stub files, they should be placed next to each other. We need a way to do that with Maturin. Also, in order to mark our package as typing-enabled we need to add an empty file named `py.typed` to the package.
#### If you do not have other Python files
If you do not need to add any other Python files apart from `pyi` to the package, the Maturin provides a way to do most of the work for you. As documented in [Maturin Guide](https://github.com/PyO3/maturin/blob/084cfaced651b28616aeea1f818bdc933a536bfe/guide/src/project_layout.md#adding-python-type-information) the only thing you need to do is create a stub file for your module named `<module_name>.pyi` in your project root and Maturin will do the rest.
```text
my-rust-project/
├── Cargo.toml
├── my_project.pyi # <<< add type stubs for Rust functions in the my_project module here
├── pyproject.toml
└── src
└── lib.rs
```
For example of `pyi` file see [`my_project.pyi` content](#my_projectpyi-content) section.
#### If you need other Python files
If you need to add other Python files apart from `pyi` to the package, you can do it also, but that requires some more work. Maturin provides easy way to add files to package ([documentation](https://github.com/PyO3/maturin/blob/0dee40510083c03607834c821eea76964140a126/Readme.md#mixed-rustpython-projects)). You just need to create a folder with the name of your module next to the `Cargo.toml` file (for customization see documentation linked above).
The folder structure would be:
```text
my-project
├── Cargo.toml
├── my_project
│ ├── __init__.py
│ ├── my_project.pyi
│ ├── other_python_file.py
│ └── py.typed
├── pyproject.toml
├── Readme.md
└── src
└── lib.rs
```
Let's go a little bit more into details on the files inside the package folder.
##### `__init__.py` content
As we now specify our own package content, we have to provide the `__init__.py` file, so the folder is treated as a package and we can import things from it. We can always use the same content that the Maturin creates for us if we do not specify a python source folder. For PyO3 bindings it would be:
```python
from .my_project import *
```
That way everything that is exposed by our native module can be imported directly from the package.
##### `py.typed` requirement
As stated in [PEP561](https://www.python.org/dev/peps/pep-0561/):
> Package maintainers who wish to support type checking of their code MUST add a marker file named py.typed to their package supporting typing. This marker applies recursively: if a top-level package includes it, all its sub-packages MUST support type checking as well.
If we do not include that file, some IDEs might still use our `pyi` files to show hints, but the type checkers might not. MyPy will raise an error in this situation:
```text
error: Skipping analyzing "my_project": found module but no type hints or library stubs
```
The file is just a marker file, so it should be empty.
##### `my_project.pyi` content
Our module stub file. This document does not aim at describing how to write them, since you can find a lot of documentation on it, starting from already quoted [PEP484](https://www.python.org/dev/peps/pep-0484/#stub-files).
The example can look like this:
```python
class Car:
"""
A class representing a car.
:param body_type: the name of body type, e.g. hatchback, sedan
:param horsepower: power of the engine in horsepower
"""
def __init__(self, body_type: str, horsepower: int) -> None: ...
@classmethod
def from_unique_name(cls, name: str) -> 'Car':
"""
Creates a Car based on unique name
:param name: model name of a car to be created
:return: a Car instance with default data
"""
def best_color(self) -> str:
"""
Gets the best color for the car.
:return: the name of the color our great algorithm thinks is the best for this car
"""
```

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ pub use impl_::{BuildFlag, BuildFlags, InterpreterConfig, PythonImplementation,
/// ///
/// | Flag | Description | /// | 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(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. | /// | `#[cfg(PyPy)]` | This marks code which is run when compiling for PyPy. |
/// ///

View File

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

View File

@ -1,13 +1,10 @@
use syn::{ use syn::{
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
punctuated::Punctuated, punctuated::Punctuated,
spanned::Spanned,
token::Comma, token::Comma,
Attribute, ExprPath, Ident, LitStr, Result, Token, Attribute, ExprPath, Ident, LitStr, Result, Token,
}; };
use crate::deprecations::{Deprecation, Deprecations};
pub mod kw { pub mod kw {
syn::custom_keyword!(annotation); syn::custom_keyword!(annotation);
syn::custom_keyword!(attribute); syn::custom_keyword!(attribute);
@ -113,66 +110,3 @@ pub fn take_pyo3_options<T: Parse>(attrs: &mut Vec<syn::Attribute>) -> Result<Ve
})?; })?;
Ok(out) 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, can_coexist: true,
} }
} }
const fn new(name: &'static str, proto: &'static str) -> Self {
PyMethod {
name,
proto,
can_coexist: false,
}
}
} }
/// Represents a slot definition. /// Represents a slot definition.
@ -156,20 +149,13 @@ pub const OBJECT: Proto = Proto {
.has_self(), .has_self(),
MethodProto::new("__str__", "PyObjectStrProtocol").has_self(), MethodProto::new("__str__", "PyObjectStrProtocol").has_self(),
MethodProto::new("__repr__", "PyObjectReprProtocol").has_self(), MethodProto::new("__repr__", "PyObjectReprProtocol").has_self(),
MethodProto::new("__format__", "PyObjectFormatProtocol")
.args(&["Format"])
.has_self(),
MethodProto::new("__hash__", "PyObjectHashProtocol").has_self(), MethodProto::new("__hash__", "PyObjectHashProtocol").has_self(),
MethodProto::new("__bytes__", "PyObjectBytesProtocol").has_self(),
MethodProto::new("__richcmp__", "PyObjectRichcmpProtocol") MethodProto::new("__richcmp__", "PyObjectRichcmpProtocol")
.args(&["Other"]) .args(&["Other"])
.has_self(), .has_self(),
MethodProto::new("__bool__", "PyObjectBoolProtocol").has_self(), MethodProto::new("__bool__", "PyObjectBoolProtocol").has_self(),
], ],
py_methods: &[ py_methods: &[],
PyMethod::new("__format__", "FormatProtocolImpl"),
PyMethod::new("__bytes__", "BytesProtocolImpl"),
],
slot_defs: &[ slot_defs: &[
SlotDef::new(&["__str__"], "Py_tp_str", "str"), SlotDef::new(&["__str__"], "Py_tp_str", "str"),
SlotDef::new(&["__repr__"], "Py_tp_repr", "repr"), SlotDef::new(&["__repr__"], "Py_tp_repr", "repr"),
@ -194,15 +180,8 @@ pub const ASYNC: Proto = Proto {
MethodProto::new("__await__", "PyAsyncAwaitProtocol").args(&["Receiver"]), MethodProto::new("__await__", "PyAsyncAwaitProtocol").args(&["Receiver"]),
MethodProto::new("__aiter__", "PyAsyncAiterProtocol").args(&["Receiver"]), MethodProto::new("__aiter__", "PyAsyncAiterProtocol").args(&["Receiver"]),
MethodProto::new("__anext__", "PyAsyncAnextProtocol").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: &[ slot_defs: &[
SlotDef::new(&["__await__"], "Py_am_await", "await_"), SlotDef::new(&["__await__"], "Py_am_await", "await_"),
SlotDef::new(&["__aiter__"], "Py_am_aiter", "aiter"), 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 { pub const GC: Proto = Proto {
name: "GC", name: "GC",
module: "::pyo3::class::gc", module: "::pyo3::class::gc",
@ -268,17 +231,8 @@ pub const DESCR: Proto = Proto {
methods: &[ methods: &[
MethodProto::new("__get__", "PyDescrGetProtocol").args(&["Receiver", "Inst", "Owner"]), MethodProto::new("__get__", "PyDescrGetProtocol").args(&["Receiver", "Inst", "Owner"]),
MethodProto::new("__set__", "PyDescrSetProtocol").args(&["Receiver", "Inst", "Value"]), 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: &[ slot_defs: &[
SlotDef::new(&["__get__"], "Py_tp_descr_get", "descr_get"), SlotDef::new(&["__get__"], "Py_tp_descr_get", "descr_get"),
SlotDef::new(&["__set__"], "Py_tp_descr_set", "descr_set"), SlotDef::new(&["__set__"], "Py_tp_descr_set", "descr_set"),
@ -313,12 +267,8 @@ pub const MAPPING: Proto = Proto {
MethodProto::new("__delitem__", "PyMappingDelItemProtocol") MethodProto::new("__delitem__", "PyMappingDelItemProtocol")
.args(&["Key"]) .args(&["Key"])
.has_self(), .has_self(),
MethodProto::new("__reversed__", "PyMappingReversedProtocol").has_self(),
], ],
py_methods: &[PyMethod::new( py_methods: &[],
"__reversed__",
"PyMappingReversedProtocolImpl",
)],
slot_defs: &[ slot_defs: &[
SlotDef::new(&["__len__"], "Py_mp_length", "len"), SlotDef::new(&["__len__"], "Py_mp_length", "len"),
SlotDef::new(&["__getitem__"], "Py_mp_subscript", "getitem"), SlotDef::new(&["__getitem__"], "Py_mp_subscript", "getitem"),
@ -492,13 +442,9 @@ pub const NUM: Proto = Proto {
MethodProto::new("__pos__", "PyNumberPosProtocol").has_self(), MethodProto::new("__pos__", "PyNumberPosProtocol").has_self(),
MethodProto::new("__abs__", "PyNumberAbsProtocol").has_self(), MethodProto::new("__abs__", "PyNumberAbsProtocol").has_self(),
MethodProto::new("__invert__", "PyNumberInvertProtocol").has_self(), MethodProto::new("__invert__", "PyNumberInvertProtocol").has_self(),
MethodProto::new("__complex__", "PyNumberComplexProtocol").has_self(),
MethodProto::new("__int__", "PyNumberIntProtocol").has_self(), MethodProto::new("__int__", "PyNumberIntProtocol").has_self(),
MethodProto::new("__float__", "PyNumberFloatProtocol").has_self(), MethodProto::new("__float__", "PyNumberFloatProtocol").has_self(),
MethodProto::new("__index__", "PyNumberIndexProtocol").has_self(), MethodProto::new("__index__", "PyNumberIndexProtocol").has_self(),
MethodProto::new("__round__", "PyNumberRoundProtocol")
.args(&["NDigits"])
.has_self(),
], ],
py_methods: &[ py_methods: &[
PyMethod::coexist("__radd__", "PyNumberRAddProtocolImpl"), PyMethod::coexist("__radd__", "PyNumberRAddProtocolImpl"),
@ -515,8 +461,6 @@ pub const NUM: Proto = Proto {
PyMethod::coexist("__rand__", "PyNumberRAndProtocolImpl"), PyMethod::coexist("__rand__", "PyNumberRAndProtocolImpl"),
PyMethod::coexist("__rxor__", "PyNumberRXorProtocolImpl"), PyMethod::coexist("__rxor__", "PyNumberRXorProtocolImpl"),
PyMethod::coexist("__ror__", "PyNumberROrProtocolImpl"), PyMethod::coexist("__ror__", "PyNumberROrProtocolImpl"),
PyMethod::new("__complex__", "PyNumberComplexProtocolImpl"),
PyMethod::new("__round__", "PyNumberRoundProtocolImpl"),
], ],
slot_defs: &[ slot_defs: &[
SlotDef::new(&["__add__", "__radd__"], "Py_nb_add", "add_radd"), 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}; use quote::{quote_spanned, ToTokens};
pub enum Deprecation { pub enum Deprecation {
NameAttribute,
PyfnNameArgument,
PyModuleNameArgument,
TextSignatureAttribute,
CallAttribute, CallAttribute,
} }
impl Deprecation { impl Deprecation {
fn ident(&self, span: Span) -> syn::Ident { fn ident(&self, span: Span) -> syn::Ident {
let string = match self { let string = match self {
Deprecation::NameAttribute => "NAME_ATTRIBUTE",
Deprecation::PyfnNameArgument => "PYFN_NAME_ARGUMENT",
Deprecation::PyModuleNameArgument => "PYMODULE_NAME_ARGUMENT",
Deprecation::TextSignatureAttribute => "TEXT_SIGNATURE_ATTRIBUTE",
Deprecation::CallAttribute => "CALL_ATTRIBUTE", Deprecation::CallAttribute => "CALL_ATTRIBUTE",
}; };
syn::Ident::new(string, span) syn::Ident::new(string, span)

View File

@ -1,11 +1,10 @@
use std::borrow::Cow;
use crate::{ use crate::{
attributes::{ attributes::{self, get_pyo3_options, is_attribute_ident, take_attributes, NameAttribute},
self, get_deprecated_name_attribute, get_pyo3_options, is_attribute_ident, take_attributes,
NameAttribute,
},
deprecations::Deprecations, deprecations::Deprecations,
}; };
use proc_macro2::TokenStream; use proc_macro2::{Ident, TokenStream};
use quote::quote; use quote::quote;
use syn::{ use syn::{
ext::IdentExt, ext::IdentExt,
@ -20,15 +19,18 @@ pub struct ConstSpec {
} }
impl ConstSpec { impl ConstSpec {
pub fn python_name(&self) -> Cow<Ident> {
if let Some(name) = &self.attributes.name {
Cow::Borrowed(&name.0)
} else {
Cow::Owned(self.rust_ident.unraw())
}
}
/// Null-terminated Python name /// Null-terminated Python name
pub fn null_terminated_python_name(&self) -> TokenStream { pub fn null_terminated_python_name(&self) -> TokenStream {
if let Some(name) = &self.attributes.name { let name = format!("{}\0", self.python_name());
let name = format!("{}\0", name.0);
quote!({#name}) quote!({#name})
} else {
let name = format!("{}\0", self.rust_ident.unraw().to_string());
quote!(#name)
}
} }
} }
@ -76,11 +78,6 @@ impl ConstAttributes {
} }
} }
Ok(true) Ok(true)
} else if let Some(name) =
get_deprecated_name_attribute(attr, &mut attributes.deprecations)?
{
attributes.set_name(name)?;
Ok(true)
} else { } else {
Ok(false) Ok(false)
} }

View File

@ -25,7 +25,7 @@ mod pyproto;
pub use from_pyobject::build_derive_from_pyobject; pub use from_pyobject::build_derive_from_pyobject;
pub use module::{process_functions_in_module, py_init, PyModuleOptions}; 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 pyfunction::{build_py_function, PyFunctionOptions};
pub use pyimpl::{build_py_methods, PyClassMethodsType}; pub use pyimpl::{build_py_methods, PyClassMethodsType};
pub use pyproto::build_py_proto; pub use pyproto::build_py_proto;

View File

@ -352,15 +352,12 @@ impl<'a> FnSpec<'a> {
parse_method_receiver(first_arg) parse_method_receiver(first_arg)
}; };
#[allow(clippy::manual_strip)] // for strip_prefix replacement supporting rust < 1.45
// strip get_ or set_ // strip get_ or set_
let strip_fn_name = |prefix: &'static str| { let strip_fn_name = |prefix: &'static str| {
let ident = name.unraw().to_string(); name.unraw()
if ident.starts_with(prefix) { .to_string()
Some(syn::Ident::new(&ident[prefix.len()..], ident.span())) .strip_prefix(prefix)
} else { .map(|stripped| syn::Ident::new(stripped, name.span()))
None
}
}; };
let (fn_type, skip_first_arg, fixed_convention) = match fn_type_attr { let (fn_type, skip_first_arg, fixed_convention) = match fn_type_attr {

View File

@ -2,15 +2,10 @@
//! Code generation for the function that initializes a python module and adds classes and function. //! Code generation for the function that initializes a python module and adds classes and function.
use crate::{ use crate::{
attributes::{self, take_pyo3_options}, attributes::{self, is_attribute_ident, take_attributes, take_pyo3_options, NameAttribute},
deprecations::Deprecations,
pyfunction::{impl_wrap_pyfunction, PyFunctionOptions}, pyfunction::{impl_wrap_pyfunction, PyFunctionOptions},
utils::PythonDoc, utils::PythonDoc,
}; };
use crate::{
attributes::{is_attribute_ident, take_attributes, NameAttribute},
deprecations::Deprecation,
};
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::quote; use quote::quote;
use syn::{ use syn::{
@ -23,23 +18,11 @@ use syn::{
pub struct PyModuleOptions { pub struct PyModuleOptions {
name: Option<syn::Ident>, name: Option<syn::Ident>,
deprecations: Deprecations,
} }
impl PyModuleOptions { impl PyModuleOptions {
pub fn from_pymodule_arg_and_attrs( pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
deprecated_pymodule_name_arg: Option<syn::Ident>, let mut options: PyModuleOptions = PyModuleOptions { name: None };
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,
};
for option in take_pyo3_options(attrs)? { for option in take_pyo3_options(attrs)? {
match option { match option {
@ -65,7 +48,6 @@ impl PyModuleOptions {
/// module /// module
pub fn py_init(fnname: &Ident, options: PyModuleOptions, doc: PythonDoc) -> TokenStream { pub fn py_init(fnname: &Ident, options: PyModuleOptions, doc: PythonDoc) -> TokenStream {
let name = options.name.unwrap_or_else(|| fnname.unraw()); let name = options.name.unwrap_or_else(|| fnname.unraw());
let deprecations = options.deprecations;
let cb_name = Ident::new(&format!("PyInit_{}", name), Span::call_site()); let cb_name = Ident::new(&format!("PyInit_{}", name), Span::call_site());
quote! { quote! {
@ -79,8 +61,6 @@ pub fn py_init(fnname: &Ident, options: PyModuleOptions, doc: PythonDoc) -> Toke
static DOC: &str = #doc; static DOC: &str = #doc;
static MODULE_DEF: ModuleDef = unsafe { ModuleDef::new(NAME, 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 +109,10 @@ impl Parse for PyFnArgs {
let _: Comma = input.parse()?; let _: Comma = input.parse()?;
let mut deprecated_name_argument = None; Ok(Self {
if let Ok(lit_str) = input.parse::<syn::LitStr>() { modname,
deprecated_name_argument = Some(lit_str); options: input.parse()?,
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 })
} }
} }
@ -167,7 +134,9 @@ fn get_pyfn_attr(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<PyFnArgs
})?; })?;
if let Some(pyfn_args) = &mut pyfn_args { 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) Ok(pyfn_args)

View File

@ -15,13 +15,11 @@ pub struct MethodProto {
} }
impl 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 { pub const fn new(name: &'static str, proto: &'static str) -> Self {
MethodProto { MethodProto {
name, name,
proto, proto,
args: MethodProto::EMPTY_ARGS, args: &[],
with_self: false, with_self: false,
with_result: true, with_result: true,
} }

View File

@ -1,11 +1,9 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use crate::attributes::{ use crate::attributes::{self, take_pyo3_options, NameAttribute, TextSignatureAttribute};
self, take_deprecated_text_signature_attribute, take_pyo3_options, NameAttribute,
TextSignatureAttribute,
};
use crate::deprecations::Deprecations; use crate::deprecations::Deprecations;
use crate::pyimpl::PyClassMethodsType; use crate::konst::{ConstAttributes, ConstSpec};
use crate::pyimpl::{gen_py_const, PyClassMethodsType};
use crate::pymethod::{impl_py_getter_def, impl_py_setter_def, PropertyType}; use crate::pymethod::{impl_py_getter_def, impl_py_setter_def, PropertyType};
use crate::utils::{self, unwrap_group, PythonDoc}; use crate::utils::{self, unwrap_group, PythonDoc};
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
@ -13,7 +11,14 @@ use quote::quote;
use syn::ext::IdentExt; use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream}; use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated; 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 /// The parsed arguments of the pyclass macro
pub struct PyClassArgs { pub struct PyClassArgs {
@ -26,24 +31,30 @@ pub struct PyClassArgs {
pub is_basetype: bool, pub is_basetype: bool,
pub has_extends: bool, pub has_extends: bool,
pub has_unsendable: bool, pub has_unsendable: bool,
pub module: Option<syn::LitStr>,
pub is_immutable: bool, pub is_immutable: bool,
pub module: Option<syn::LitStr>,
pub class_kind: PyClassKind,
} }
impl Parse for PyClassArgs { impl PyClassArgs {
fn parse(input: ParseStream) -> Result<Self> { fn parse(input: ParseStream, kind: PyClassKind) -> Result<Self> {
let mut slf = PyClassArgs::default(); let mut slf = PyClassArgs::new(kind);
let vars = Punctuated::<Expr, Token![,]>::parse_terminated(input)?; let vars = Punctuated::<Expr, Token![,]>::parse_terminated(input)?;
for expr in vars { for expr in vars {
slf.add_expr(&expr)?; slf.add_expr(&expr)?;
} }
Ok(slf) Ok(slf)
} }
}
impl Default for PyClassArgs { pub fn parse_stuct_args(input: ParseStream) -> syn::Result<Self> {
fn default() -> 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 { PyClassArgs {
freelist: None, freelist: None,
name: None, name: None,
@ -56,11 +67,10 @@ impl Default for PyClassArgs {
has_extends: false, has_extends: false,
has_unsendable: false, has_unsendable: false,
is_immutable: false, is_immutable: false,
class_kind,
} }
} }
}
impl PyClassArgs {
/// Adda single expression from the comma separated list in the attribute, which is /// Adda single expression from the comma separated list in the attribute, which is
/// either a single word or an assignment expression /// either a single word or an assignment expression
fn add_expr(&mut self, expr: &Expr) -> Result<()> { fn add_expr(&mut self, expr: &Expr) -> Result<()> {
@ -118,6 +128,9 @@ impl PyClassArgs {
}, },
"extends" => match unwrap_group(&**right) { "extends" => match unwrap_group(&**right) {
syn::Expr::Path(exp) => { syn::Expr::Path(exp) => {
if self.class_kind == PyClassKind::Enum {
bail_spanned!( assign.span() => "enums cannot extend from other classes" );
}
self.base = syn::TypePath { self.base = syn::TypePath {
path: exp.path.clone(), path: exp.path.clone(),
qself: None, qself: None,
@ -152,6 +165,9 @@ impl PyClassArgs {
self.has_weaklist = true; self.has_weaklist = true;
} }
"subclass" => { "subclass" => {
if self.class_kind == PyClassKind::Enum {
bail_spanned!(exp.span() => "enums can't be inherited by other classes");
}
self.is_basetype = true; self.is_basetype = true;
} }
"dict" => { "dict" => {
@ -223,12 +239,7 @@ pub fn build_py_class(
args: &PyClassArgs, args: &PyClassArgs,
methods_type: PyClassMethodsType, methods_type: PyClassMethodsType,
) -> syn::Result<TokenStream> { ) -> syn::Result<TokenStream> {
let mut options = PyClassPyO3Options::take_pyo3_options(&mut class.attrs)?; let options = PyClassPyO3Options::take_pyo3_options(&mut class.attrs)?;
if let Some(text_signature) =
take_deprecated_text_signature_attribute(&mut class.attrs, &mut options.deprecations)?
{
options.set_text_signature(text_signature)?;
}
let doc = utils::get_doc( let doc = utils::get_doc(
&class.attrs, &class.attrs,
options options
@ -341,41 +352,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 { fn get_class_python_name<'a>(cls: &'a syn::Ident, attr: &'a PyClassArgs) -> &'a syn::Ident {
attr.name.as_ref().unwrap_or(cls) attr.name.as_ref().unwrap_or(cls)
} }
@ -388,278 +364,121 @@ fn impl_class(
methods_type: PyClassMethodsType, methods_type: PyClassMethodsType,
deprecations: Deprecations, deprecations: Deprecations,
) -> syn::Result<TokenStream> { ) -> 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| { let py_class_impl = PyClassImplsBuilder::new(cls, attr, methods_type)
quote! { .doc(doc)
impl ::pyo3::class::impl_::PyClassWithFreeList for #cls { .impl_all();
#[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 descriptors = impl_descriptors(cls, field_options)?; let descriptors = impl_descriptors(cls, field_options)?;
// insert space for weak ref Ok(quote! {
let weakref = if attr.has_weaklist { #pytypeinfo_impl
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 #py_class_impl
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>>() {} #descriptors
_assert_implements_protocol::<#cls>(); })
} }
}
} else {
quote! {}
};
let (impl_inventory, for_each_py_method) = match methods_type { struct PyClassEnumVariant<'a> {
PyClassMethodsType::Specialization => (None, quote! { visitor(collector.py_methods()); }), ident: &'a syn::Ident,
PyClassMethodsType::Inventory => ( /* currently have no more options */
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 { pub fn build_py_enum(
PyClassMethodsType::Specialization => { enum_: &syn::ItemEnum,
quote! { visitor(collector.methods_protocol_slots()); } args: PyClassArgs,
} method_type: PyClassMethodsType,
PyClassMethodsType::Inventory => { ) -> syn::Result<TokenStream> {
quote! { let variants: Vec<PyClassEnumVariant> = enum_
for inventory in ::pyo3::inventory::iter::<<Self as ::pyo3::class::impl_::HasMethodsInventory>::Methods>() { .variants
visitor(::pyo3::class::impl_::PyMethodsInventory::slots(inventory)); .iter()
} .map(|v| extract_variant_data(v))
} .collect::<syn::Result<_>>()?;
} impl_enum(enum_, args, variants, method_type)
}; }
let base = &attr.base; fn impl_enum(
let base_nativetype = if attr.has_extends { enum_: &syn::ItemEnum,
quote! { <Self::BaseType as ::pyo3::class::impl_::PyClassBaseType>::BaseNativeType } attrs: PyClassArgs,
} else { variants: Vec<PyClassEnumVariant>,
quote! { ::pyo3::PyAny } methods_type: PyClassMethodsType,
}; ) -> syn::Result<TokenStream> {
let enum_name = &enum_.ident;
// If #cls is not extended type, we allow Self->PyObject conversion let doc = utils::get_doc(&enum_.attrs, None);
let into_pyobject = if !attr.has_extends { let enum_cls = impl_enum_class(enum_name, &attrs, variants, doc, methods_type)?;
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;
let mutability = if attr.is_immutable {
quote! {
unsafe impl ::pyo3::pyclass::ImmutablePyClass for #cls {}
unsafe impl ::pyo3::class::impl_::BorrowImpl for #cls {
fn get_borrow_flag() -> for<'r> fn(&'r ::pyo3::pycell::PyCell<Self>) -> ::pyo3::pycell::BorrowFlag
where Self: ::pyo3::PyClass
{
::pyo3::pycell::impl_::get_borrow_flag_dummy
}
fn increment_borrow_flag() -> for<'r> fn(&'r ::pyo3::pycell::PyCell<Self>, ::pyo3::pycell::BorrowFlag)
where Self: ::pyo3::PyClass
{
::pyo3::pycell::impl_::increment_borrow_flag_dummy
}
fn decrement_borrow_flag() -> for<'r> fn(&'r ::pyo3::pycell::PyCell<Self>, ::pyo3::pycell::BorrowFlag)
where Self: ::pyo3::PyClass
{
::pyo3::pycell::impl_::decrement_borrow_flag_dummy
}
}
}
} else {
quote! {
unsafe impl ::pyo3::pyclass::MutablePyClass for #cls {}
unsafe impl ::pyo3::class::impl_::BorrowImpl for #cls {}
impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a mut #cls
{
type Target = ::pyo3::PyRefMut<'a, #cls>;
}
}
};
Ok(quote! { Ok(quote! {
unsafe impl ::pyo3::type_object::PyTypeInfo for #cls { #enum_cls
type AsRefTarget = ::pyo3::PyCell<Self>; })
}
const NAME: &'static str = #cls_name; fn impl_enum_class(
const MODULE: ::std::option::Option<&'static str> = #module; cls: &syn::Ident,
attr: &PyClassArgs,
variants: Vec<PyClassEnumVariant>,
doc: PythonDoc,
methods_type: PyClassMethodsType,
) -> syn::Result<TokenStream> {
let pytypeinfo = impl_pytypeinfo(cls, attr, None);
let pyclass_impls = PyClassImplsBuilder::new(cls, attr, methods_type)
.doc(doc)
.impl_all();
let descriptors = unit_variants_as_descriptors(cls, variants.iter().map(|v| v.ident));
#[inline] Ok(quote! {
fn type_object_raw(py: ::pyo3::Python<'_>) -> *mut ::pyo3::ffi::PyTypeObject {
#deprecations
use ::pyo3::type_object::LazyStaticType; #pytypeinfo
static TYPE_OBJECT: LazyStaticType = LazyStaticType::new();
TYPE_OBJECT.get_or_init::<Self>(py)
}
}
impl ::pyo3::PyClass for #cls { #pyclass_impls
type Dict = #dict;
type WeakRef = #weakref;
type BaseNativeType = #base_nativetype;
}
#mutability
impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a #cls
{
type Target = ::pyo3::PyRef<'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 #descriptors
#gc_impl
}) })
} }
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( fn impl_descriptors(
cls: &syn::Ident, cls: &syn::Ident,
field_options: Vec<(&syn::Field, FieldPyO3Options)>, field_options: Vec<(&syn::Field, FieldPyO3Options)>,
@ -710,3 +529,387 @@ fn impl_descriptors(
} }
}) })
} }
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_methods_inventory(),
self.impl_pyclassimpl(),
self.impl_mutability(),
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;
if self.attr.is_immutable {
quote! {
impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a #cls
{
type Target = ::pyo3::PyRef<'a, #cls>;
}
}
} else{
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! {}
}
}
/// To allow multiple #[pymethods] block, we define inventory types.
fn impl_methods_inventory(&self) -> TokenStream {
let cls = self.cls;
let methods_type = self.methods_type;
match methods_type {
PyClassMethodsType::Specialization => quote! {},
PyClassMethodsType::Inventory => {
// 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 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 methods_protos = match self.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 for_each_py_method = match self.methods_type {
PyClassMethodsType::Specialization => quote! { visitor(collector.py_methods()); },
PyClassMethodsType::Inventory => quote! {
for inventory in ::pyo3::inventory::iter::<<Self as ::pyo3::class::impl_::HasMethodsInventory>::Methods>() {
visitor(::pyo3::class::impl_::PyMethodsInventory::methods(inventory));
}
},
};
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;
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();
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()
}
}
}
}
fn impl_mutability(&self) -> TokenStream {
let cls = self.cls;
if self.attr.is_immutable {
quote! {
unsafe impl ::pyo3::pyclass::ImmutablePyClass for #cls {}
unsafe impl ::pyo3::class::impl_::BorrowImpl for #cls {
fn get_borrow_flag() -> for<'r> fn(&'r ::pyo3::pycell::PyCell<Self>) -> ::pyo3::pycell::BorrowFlag
where Self: ::pyo3::PyClass
{
::pyo3::pycell::impl_::get_borrow_flag_dummy
}
fn increment_borrow_flag() -> for<'r> fn(&'r ::pyo3::pycell::PyCell<Self>, ::pyo3::pycell::BorrowFlag)
where Self: ::pyo3::PyClass
{
::pyo3::pycell::impl_::increment_borrow_flag_dummy
}
fn decrement_borrow_flag() -> for<'r> fn(&'r ::pyo3::pycell::PyCell<Self>, ::pyo3::pycell::BorrowFlag)
where Self: ::pyo3::PyClass
{
::pyo3::pycell::impl_::decrement_borrow_flag_dummy
}
}
}
} else {
quote! {
unsafe impl ::pyo3::pyclass::MutablePyClass for #cls {}
unsafe impl ::pyo3::class::impl_::BorrowImpl for #cls {}
}
}
}
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! {}
}
}
}

View File

@ -2,9 +2,8 @@
use crate::{ use crate::{
attributes::{ attributes::{
self, get_deprecated_name_attribute, get_deprecated_text_signature_attribute, self, get_pyo3_options, take_attributes, take_pyo3_options, FromPyWithAttribute,
get_pyo3_options, take_attributes, FromPyWithAttribute, NameAttribute, NameAttribute, TextSignatureAttribute,
TextSignatureAttribute,
}, },
deprecations::Deprecations, deprecations::Deprecations,
method::{self, CallingConvention, FnArg}, method::{self, CallingConvention, FnArg},
@ -303,34 +302,10 @@ impl Parse for PyFunctionOption {
impl PyFunctionOptions { impl PyFunctionOptions {
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> { pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
let mut options = PyFunctionOptions::default(); let mut options = PyFunctionOptions::default();
options.take_pyo3_options(attrs)?; options.add_attributes(take_pyo3_options(attrs)?)?;
Ok(options) 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( pub fn add_attributes(
&mut self, &mut self,
attrs: impl IntoIterator<Item = PyFunctionOption>, attrs: impl IntoIterator<Item = PyFunctionOption>,
@ -379,7 +354,7 @@ pub fn build_py_function(
ast: &mut syn::ItemFn, ast: &mut syn::ItemFn,
mut options: PyFunctionOptions, mut options: PyFunctionOptions,
) -> syn::Result<TokenStream> { ) -> 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) Ok(impl_wrap_pyfunction(ast, options)?.1)
} }

View File

@ -5,7 +5,7 @@ use std::collections::HashSet;
use crate::{ use crate::{
konst::{ConstAttributes, ConstSpec}, konst::{ConstAttributes, ConstSpec},
pyfunction::PyFunctionOptions, pyfunction::PyFunctionOptions,
pymethod, pymethod::{self, is_proto_method},
}; };
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use pymethod::GeneratedPyMethod; use pymethod::GeneratedPyMethod;
@ -13,6 +13,7 @@ use quote::quote;
use syn::spanned::Spanned; use syn::spanned::Spanned;
/// The mechanism used to collect `#[pymethods]` into the type object /// The mechanism used to collect `#[pymethods]` into the type object
#[derive(Copy, Clone)]
pub enum PyClassMethodsType { pub enum PyClassMethodsType {
Specialization, Specialization,
Inventory, Inventory,
@ -79,6 +80,13 @@ pub fn impl_methods(
let attrs = get_cfg_attributes(&konst.attrs); let attrs = get_cfg_attributes(&konst.attrs);
let meth = gen_py_const(ty, &spec); let meth = gen_py_const(ty, &spec);
methods.push(quote!(#(#attrs)* #meth)); methods.push(quote!(#(#attrs)* #meth));
if is_proto_method(&spec.python_name().to_string()) {
// If this is a known protocol method e.g. __contains__, then allow this
// symbol even though it's not an uppercase constant.
konst
.attrs
.push(syn::parse_quote!(#[allow(non_upper_case_globals)]));
}
} }
} }
_ => (), _ => (),
@ -111,7 +119,7 @@ pub fn impl_methods(
}) })
} }
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 member = &spec.rust_ident;
let deprecations = &spec.attributes.deprecations; let deprecations = &spec.attributes.deprecations;
let python_name = &spec.null_terminated_python_name(); let python_name = &spec.null_terminated_python_name();

View File

@ -24,6 +24,63 @@ pub enum GeneratedPyMethod {
SlotTraitImpl(String, TokenStream), SlotTraitImpl(String, TokenStream),
} }
pub struct PyMethod<'a> {
kind: PyMethodKind,
method_name: String,
spec: FnSpec<'a>,
}
enum PyMethodKind {
Fn,
Proto(PyMethodProtoKind),
}
impl PyMethodKind {
fn from_name(name: &str) -> Self {
if let Some(slot_def) = pyproto(name) {
PyMethodKind::Proto(PyMethodProtoKind::Slot(slot_def))
} else if name == "__call__" {
PyMethodKind::Proto(PyMethodProtoKind::Call)
} else if let Some(slot_fragment_def) = pyproto_fragment(name) {
PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(slot_fragment_def))
} else {
PyMethodKind::Fn
}
}
}
enum PyMethodProtoKind {
Slot(&'static SlotDef),
Call,
SlotFragment(&'static SlotFragmentDef),
}
impl<'a> PyMethod<'a> {
fn parse(
sig: &'a mut syn::Signature,
meth_attrs: &mut Vec<syn::Attribute>,
options: PyFunctionOptions,
) -> Result<Self> {
let spec = FnSpec::parse(sig, meth_attrs, options)?;
let method_name = spec.python_name.to_string();
let kind = PyMethodKind::from_name(&method_name);
Ok(Self {
kind,
method_name,
spec,
})
}
}
pub fn is_proto_method(name: &str) -> bool {
match PyMethodKind::from_name(name) {
PyMethodKind::Fn => false,
PyMethodKind::Proto(_) => true,
}
}
pub fn gen_py_method( pub fn gen_py_method(
cls: &syn::Type, cls: &syn::Type,
sig: &mut syn::Signature, sig: &mut syn::Signature,
@ -33,56 +90,55 @@ pub fn gen_py_method(
check_generic(sig)?; check_generic(sig)?;
ensure_not_async_fn(sig)?; ensure_not_async_fn(sig)?;
ensure_function_options_valid(&options)?; ensure_function_options_valid(&options)?;
let spec = FnSpec::parse(sig, &mut *meth_attrs, options)?; let method = PyMethod::parse(sig, &mut *meth_attrs, options)?;
let spec = &method.spec;
let method_name = spec.python_name.to_string(); Ok(match (method.kind, &spec.tp) {
// Class attributes go before protos so that class attributes can be used to set proto
if let Some(slot_def) = pyproto(&method_name) { // method to None.
ensure_no_forbidden_protocol_attributes(&spec, &method_name)?; (_, FnType::ClassAttribute) => {
let slot = slot_def.generate_type_slot(cls, &spec)?; GeneratedPyMethod::Method(impl_py_class_attribute(cls, spec))
return Ok(GeneratedPyMethod::Proto(slot)); }
} else if method_name == "__call__" { (PyMethodKind::Proto(proto_kind), _) => {
ensure_no_forbidden_protocol_attributes(&spec, &method_name)?; ensure_no_forbidden_protocol_attributes(spec, &method.method_name)?;
return Ok(GeneratedPyMethod::Proto(impl_call_slot(cls, spec)?)); match proto_kind {
PyMethodProtoKind::Slot(slot_def) => {
let slot = slot_def.generate_type_slot(cls, spec)?;
GeneratedPyMethod::Proto(slot)
}
PyMethodProtoKind::Call => {
GeneratedPyMethod::Proto(impl_call_slot(cls, method.spec)?)
}
PyMethodProtoKind::SlotFragment(slot_fragment_def) => {
let proto = slot_fragment_def.generate_pyproto_fragment(cls, spec)?;
GeneratedPyMethod::SlotTraitImpl(method.method_name, proto)
}
} }
if let Some(slot_fragment_def) = pyproto_fragment(&method_name) {
ensure_no_forbidden_protocol_attributes(&spec, &method_name)?;
let proto = slot_fragment_def.generate_pyproto_fragment(cls, &spec)?;
return Ok(GeneratedPyMethod::SlotTraitImpl(method_name, proto));
} }
Ok(match &spec.tp {
// ordinary functions (with some specialties) // ordinary functions (with some specialties)
FnType::Fn(_) => GeneratedPyMethod::Method(impl_py_method_def(cls, &spec, None)?), (_, FnType::Fn(_)) => GeneratedPyMethod::Method(impl_py_method_def(cls, spec, None)?),
FnType::FnClass => GeneratedPyMethod::Method(impl_py_method_def( (_, FnType::FnClass) => GeneratedPyMethod::Method(impl_py_method_def(
cls, cls,
&spec, spec,
Some(quote!(::pyo3::ffi::METH_CLASS)), Some(quote!(::pyo3::ffi::METH_CLASS)),
)?), )?),
FnType::FnStatic => GeneratedPyMethod::Method(impl_py_method_def( (_, FnType::FnStatic) => GeneratedPyMethod::Method(impl_py_method_def(
cls, cls,
&spec, spec,
Some(quote!(::pyo3::ffi::METH_STATIC)), Some(quote!(::pyo3::ffi::METH_STATIC)),
)?), )?),
// special prototypes // special prototypes
FnType::FnNew => GeneratedPyMethod::TraitImpl(impl_py_method_def_new(cls, &spec)?), (_, FnType::FnNew) => GeneratedPyMethod::TraitImpl(impl_py_method_def_new(cls, spec)?),
FnType::ClassAttribute => GeneratedPyMethod::Method(impl_py_class_attribute(cls, &spec)),
FnType::Getter(self_type) => GeneratedPyMethod::Method(impl_py_getter_def( (_, FnType::Getter(self_type)) => GeneratedPyMethod::Method(impl_py_getter_def(
cls, cls,
PropertyType::Function { PropertyType::Function { self_type, spec },
self_type,
spec: &spec,
},
)?), )?),
FnType::Setter(self_type) => GeneratedPyMethod::Method(impl_py_setter_def( (_, FnType::Setter(self_type)) => GeneratedPyMethod::Method(impl_py_setter_def(
cls, cls,
PropertyType::Function { PropertyType::Function { self_type, spec },
self_type,
spec: &spec,
},
)?), )?),
FnType::FnModule => { (_, FnType::FnModule) => {
unreachable!("methods cannot be FnModule") unreachable!("methods cannot be FnModule")
} }
}) })
@ -343,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 /// 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]) { fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&FnArg>, &[FnArg]) {
if args match args {
.get(0) [py, args @ ..] if utils::is_python(py.ty) => (Some(py), args),
.map(|py| utils::is_python(py.ty)) args => (None, args),
.unwrap_or(false)
{
(Some(&args[0]), &args[1..])
} else {
(None, args)
} }
} }
@ -427,8 +478,8 @@ const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc")
const __RICHCMP__: SlotDef = SlotDef::new("Py_tp_richcompare", "richcmpfunc") const __RICHCMP__: SlotDef = SlotDef::new("Py_tp_richcompare", "richcmpfunc")
.extract_error_mode(ExtractErrorMode::NotImplemented) .extract_error_mode(ExtractErrorMode::NotImplemented)
.arguments(&[Ty::Object, Ty::CompareOp]); .arguments(&[Ty::Object, Ty::CompareOp]);
const __GET__: SlotDef = const __GET__: SlotDef = SlotDef::new("Py_tp_descr_get", "descrgetfunc")
SlotDef::new("Py_tp_descr_get", "descrgetfunc").arguments(&[Ty::Object, Ty::Object]); .arguments(&[Ty::MaybeNullObject, Ty::MaybeNullObject]);
const __ITER__: SlotDef = SlotDef::new("Py_tp_iter", "getiterfunc"); const __ITER__: SlotDef = SlotDef::new("Py_tp_iter", "getiterfunc");
const __NEXT__: SlotDef = SlotDef::new("Py_tp_iternext", "iternextfunc").return_conversion( const __NEXT__: SlotDef = SlotDef::new("Py_tp_iternext", "iternextfunc").return_conversion(
TokenGenerator(|| quote! { ::pyo3::class::iter::IterNextOutput::<_, _> }), TokenGenerator(|| quote! { ::pyo3::class::iter::IterNextOutput::<_, _> }),
@ -550,6 +601,7 @@ fn pyproto(method_name: &str) -> Option<&'static SlotDef> {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
enum Ty { enum Ty {
Object, Object,
MaybeNullObject,
NonNullObject, NonNullObject,
CompareOp, CompareOp,
Int, Int,
@ -561,7 +613,7 @@ enum Ty {
impl Ty { impl Ty {
fn ffi_type(self) -> TokenStream { fn ffi_type(self) -> TokenStream {
match self { match self {
Ty::Object => quote! { *mut ::pyo3::ffi::PyObject }, Ty::Object | Ty::MaybeNullObject => quote! { *mut ::pyo3::ffi::PyObject },
Ty::NonNullObject => quote! { ::std::ptr::NonNull<::pyo3::ffi::PyObject> }, Ty::NonNullObject => quote! { ::std::ptr::NonNull<::pyo3::ffi::PyObject> },
Ty::Int | Ty::CompareOp => quote! { ::std::os::raw::c_int }, Ty::Int | Ty::CompareOp => quote! { ::std::os::raw::c_int },
Ty::PyHashT => quote! { ::pyo3::ffi::Py_hash_t }, Ty::PyHashT => quote! { ::pyo3::ffi::Py_hash_t },
@ -589,6 +641,22 @@ impl Ty {
); );
extract_object(cls, arg.ty, 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)
}
Ty::NonNullObject => { Ty::NonNullObject => {
let extract = handle_error( let extract = handle_error(
extract_error_mode, extract_error_mode,

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 == "PyAsyncProtocol" => &defs::ASYNC,
Some(segment) if segment.ident == "PyMappingProtocol" => &defs::MAPPING, Some(segment) if segment.ident == "PyMappingProtocol" => &defs::MAPPING,
Some(segment) if segment.ident == "PyIterProtocol" => &defs::ITER, 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 == "PySequenceProtocol" => &defs::SEQ,
Some(segment) if segment.ident == "PyNumberProtocol" => &defs::NUM, Some(segment) if segment.ident == "PyNumberProtocol" => &defs::NUM,
Some(segment) if segment.ident == "PyDescrProtocol" => &defs::DESCR, Some(segment) if segment.ident == "PyDescrProtocol" => &defs::DESCR,

View File

@ -62,8 +62,6 @@ pub fn option_type_argument(ty: &syn::Type) -> Option<&syn::Type> {
#[derive(Clone)] #[derive(Clone)]
pub struct PythonDoc(TokenStream); 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 /// Collects all #[doc = "..."] attributes into a TokenStream evaluating to a null-terminated string
/// e.g. concat!("...", "\n", "\0") /// e.g. concat!("...", "\n", "\0")
pub fn get_doc( pub fn get_doc(
@ -107,11 +105,11 @@ pub fn get_doc(
// Strip single left space from literal strings, if needed. // Strip single left space from literal strings, if needed.
// e.g. `/// Hello world` expands to #[doc = " Hello world"] // e.g. `/// Hello world` expands to #[doc = " Hello world"]
let doc_line = lit_str.value(); let doc_line = lit_str.value();
if doc_line.starts_with(' ') { doc_line
syn::LitStr::new(&doc_line[1..], lit_str.span()).to_tokens(tokens) .strip_prefix(' ')
} else { .map(|stripped| syn::LitStr::new(stripped, lit_str.span()))
lit_str.to_tokens(tokens) .unwrap_or(lit_str)
} .to_tokens(tokens);
} else { } else {
// This is probably a macro doc from Rust 1.54, e.g. #[doc = include_str!(...)] // This is probably a macro doc from Rust 1.54, e.g. #[doc = include_str!(...)]
token_stream.to_tokens(tokens) token_stream.to_tokens(tokens)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "pyo3-macros" name = "pyo3-macros"
version = "0.15.0" version = "0.15.1"
description = "Proc macros for PyO3 package" description = "Proc macros for PyO3 package"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"] authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"] keywords = ["pyo3", "python", "cpython", "ffi"]
@ -19,4 +19,4 @@ multiple-pymethods = []
[dependencies] [dependencies]
quote = "1" quote = "1"
syn = { version = "1", features = ["full", "extra-traits"] } 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 proc_macro::TokenStream;
use pyo3_macros_backend::{ 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, build_py_proto, get_doc, process_functions_in_module, py_init, PyClassArgs, PyClassMethodsType,
PyFunctionOptions, PyModuleOptions, PyFunctionOptions, PyModuleOptions,
}; };
use quote::quote; use quote::quote;
use syn::parse_macro_input; use syn::{parse::Nothing, parse_macro_input};
/// A proc macro used to implement Python modules. /// A proc macro used to implement Python modules.
/// ///
@ -31,19 +31,11 @@ use syn::parse_macro_input;
/// ///
/// [1]: https://pyo3.rs/latest/module.html /// [1]: https://pyo3.rs/latest/module.html
#[proc_macro_attribute] #[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 mut ast = parse_macro_input!(input as syn::ItemFn);
let options = match PyModuleOptions::from_attrs(&mut ast.attrs) {
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,
) {
Ok(options) => options, Ok(options) => options,
Err(e) => return e.to_compile_error().into(), Err(e) => return e.to_compile_error().into(),
}; };
@ -115,17 +107,22 @@ pub fn pyproto(_: TokenStream, input: TokenStream) -> TokenStream {
/// [10]: https://en.wikipedia.org/wiki/Free_list /// [10]: https://en.wikipedia.org/wiki/Free_list
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream { pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream {
let methods_type = if cfg!(feature = "multiple-pymethods") { use syn::Item;
PyClassMethodsType::Inventory let item = parse_macro_input!(input as Item);
} else { match item {
PyClassMethodsType::Specialization Item::Struct(struct_) => pyclass_impl(attr, struct_, methods_type()),
}; Item::Enum(enum_) => pyclass_enum_impl(attr, enum_, methods_type()),
pyclass_impl(attr, input, 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. /// A proc macro used to expose methods to Python.
/// ///
/// Methods within a `#[pymethods]` block can be annotated with the following: /// Methods within a `#[pymethods]` block can be annotated with as well as the following:
/// ///
/// | Annotation | Description | /// | Annotation | Description |
/// | :- | :- | /// | :- | :- |
@ -135,6 +132,7 @@ pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream {
/// | [`#[classmethod]`][7] | Defines the method as a classmethod, like Python's `@classmethod` decorator.| /// | [`#[classmethod]`][7] | Defines the method as a classmethod, like Python's `@classmethod` decorator.|
/// | [`#[classattr]`][9] | Defines a class variable. | /// | [`#[classattr]`][9] | Defines a class variable. |
/// | [`#[args]`][10] | Define a method's default arguments and allows the function to receive `*args` and `**kwargs`. | /// | [`#[args]`][10] | Define a method's default arguments and allows the function to receive `*args` and `**kwargs`. |
/// | <nobr>[`#[pyo3(<option> = <value>)`][pyo3-method-options]<nobr> | Any of the `#[pyo3]` options supported on [`macro@pyfunction`]. |
/// ///
/// For more on creating class methods, /// For more on creating class methods,
/// see the [class section of the guide][1]. /// see the [class section of the guide][1].
@ -202,12 +200,11 @@ pub fn derive_from_py_object(item: TokenStream) -> TokenStream {
} }
fn pyclass_impl( fn pyclass_impl(
attr: TokenStream, attrs: TokenStream,
input: TokenStream, mut ast: syn::ItemStruct,
methods_type: PyClassMethodsType, methods_type: PyClassMethodsType,
) -> TokenStream { ) -> TokenStream {
let mut ast = parse_macro_input!(input as syn::ItemStruct); let args = parse_macro_input!(attrs with PyClassArgs::parse_stuct_args);
let args = parse_macro_input!(attr as PyClassArgs);
let expanded = let expanded =
build_py_class(&mut ast, &args, methods_type).unwrap_or_else(|e| e.to_compile_error()); build_py_class(&mut ast, &args, methods_type).unwrap_or_else(|e| e.to_compile_error());
@ -218,6 +215,22 @@ fn pyclass_impl(
.into() .into()
} }
fn pyclass_enum_impl(
attr: TokenStream,
enum_: syn::ItemEnum,
methods_type: PyClassMethodsType,
) -> TokenStream {
let args = parse_macro_input!(attr with PyClassArgs::parse_enum_args);
let expanded =
build_py_enum(&enum_, args, methods_type).unwrap_or_else(|e| e.into_compile_error());
quote!(
#enum_
#expanded
)
.into()
}
fn pymethods_impl(input: TokenStream, methods_type: PyClassMethodsType) -> TokenStream { fn pymethods_impl(input: TokenStream, methods_type: PyClassMethodsType) -> TokenStream {
let mut ast = parse_macro_input!(input as syn::ItemImpl); let mut ast = parse_macro_input!(input as syn::ItemImpl);
let expanded = let expanded =
@ -229,3 +242,11 @@ fn pymethods_impl(input: TokenStream, methods_type: PyClassMethodsType) -> Token
) )
.into() .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 { pub fn from_format(format: &CStr) -> ElementType {
match format.to_bytes() { match format.to_bytes() {
[char] | [b'@', char] => native_element_type_from_type_char(*char), [char] | [b'@', char] => native_element_type_from_type_char(*char),
[modifier, char] [modifier, char] if matches!(modifier, b'=' | b'<' | b'>' | b'!') => {
if (*modifier == b'='
|| *modifier == b'<'
|| *modifier == b'>'
|| *modifier == b'!') =>
{
standard_element_type_from_type_char(*char) standard_element_type_from_type_char(*char)
} }
_ => ElementType::Unknown, _ => ElementType::Unknown,

View File

@ -83,17 +83,6 @@ pub trait PyObjectProtocol<'p>: PyClass {
unimplemented!() 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 fn __hash__(&'p self) -> Self::Result
where where
Self: PyObjectHashProtocol<'p>, Self: PyObjectHashProtocol<'p>,
@ -101,17 +90,6 @@ pub trait PyObjectProtocol<'p>: PyClass {
unimplemented!() 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 fn __richcmp__(&'p self, other: Self::Other, op: CompareOp) -> Self::Result
where where
Self: PyObjectRichcmpProtocol<'p>, Self: PyObjectRichcmpProtocol<'p>,
@ -145,19 +123,12 @@ pub trait PyObjectStrProtocol<'p>: PyObjectProtocol<'p> {
pub trait PyObjectReprProtocol<'p>: PyObjectProtocol<'p> { pub trait PyObjectReprProtocol<'p>: PyObjectProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>; type Result: IntoPyCallbackOutput<PyObject>;
} }
pub trait PyObjectFormatProtocol<'p>: PyObjectProtocol<'p> {
type Format: FromPyObject<'p>;
type Result: IntoPyCallbackOutput<PyObject>;
}
pub trait PyObjectHashProtocol<'p>: PyObjectProtocol<'p> { pub trait PyObjectHashProtocol<'p>: PyObjectProtocol<'p> {
type Result: IntoPyCallbackOutput<HashCallbackOutput>; type Result: IntoPyCallbackOutput<HashCallbackOutput>;
} }
pub trait PyObjectBoolProtocol<'p>: PyObjectProtocol<'p> { pub trait PyObjectBoolProtocol<'p>: PyObjectProtocol<'p> {
type Result: IntoPyCallbackOutput<bool>; type Result: IntoPyCallbackOutput<bool>;
} }
pub trait PyObjectBytesProtocol<'p>: PyObjectProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>;
}
pub trait PyObjectRichcmpProtocol<'p>: PyObjectProtocol<'p> { pub trait PyObjectRichcmpProtocol<'p>: PyObjectProtocol<'p> {
type Other: FromPyObject<'p>; type Other: FromPyObject<'p>;
type Result: IntoPyCallbackOutput<PyObject>; 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) //! https://docs.python.org/3/reference/datamodel.html#implementing-descriptors)
use crate::callback::IntoPyCallbackOutput; use crate::callback::IntoPyCallbackOutput;
use crate::types::PyAny;
use crate::{FromPyObject, PyClass, PyObject}; use crate::{FromPyObject, PyClass, PyObject};
use std::os::raw::c_int; use std::os::raw::c_int;
@ -30,28 +29,6 @@ pub trait PyDescrProtocol<'p>: PyClass {
{ {
unimplemented!() 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> { pub trait PyDescrGetProtocol<'p>: PyDescrProtocol<'p> {
@ -68,15 +45,5 @@ pub trait PyDescrSetProtocol<'p>: PyDescrProtocol<'p> {
type Result: IntoPyCallbackOutput<()>; 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_get, PyDescrGetProtocol, Self::__get__);
py_ternarys_func!(descr_set, PyDescrSetProtocol, Self::__set__, c_int); py_ternarys_func!(descr_set, PyDescrSetProtocol, Self::__set__, c_int);

View File

@ -563,7 +563,6 @@ pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
/// # Safety /// # Safety
/// - `obj` must be a valid pointer to an instance of T (not a subclass). /// - `obj` must be a valid pointer to an instance of T (not a subclass).
/// - The GIL must be held. /// - 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) { pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
let obj = obj as *mut ffi::PyObject; let obj = obj as *mut ffi::PyObject;
debug_assert_eq!( debug_assert_eq!(
@ -581,12 +580,11 @@ pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_
}; };
free(obj as *mut c_void); free(obj as *mut c_void);
if cfg!(Py_3_8) { #[cfg(Py_3_8)]
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 { if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject); ffi::Py_DECREF(ty as *mut ffi::PyObject);
} }
} }
}
} }
/// Workaround for Python issue 35810; no longer necessary in Python 3.8 /// Workaround for Python issue 35810; no longer necessary in Python 3.8
@ -693,7 +691,6 @@ slots_trait!(PyMethodsProtocolSlots, methods_protocol_slots);
methods_trait!(PyObjectProtocolMethods, object_protocol_methods); methods_trait!(PyObjectProtocolMethods, object_protocol_methods);
methods_trait!(PyAsyncProtocolMethods, async_protocol_methods); methods_trait!(PyAsyncProtocolMethods, async_protocol_methods);
methods_trait!(PyContextProtocolMethods, context_protocol_methods);
methods_trait!(PyDescrProtocolMethods, descr_protocol_methods); methods_trait!(PyDescrProtocolMethods, descr_protocol_methods);
methods_trait!(PyMappingProtocolMethods, mapping_protocol_methods); methods_trait!(PyMappingProtocolMethods, mapping_protocol_methods);
methods_trait!(PyNumberProtocolMethods, number_protocol_methods); methods_trait!(PyNumberProtocolMethods, number_protocol_methods);

View File

@ -36,17 +36,6 @@ pub trait PyMappingProtocol<'p>: PyClass {
{ {
unimplemented!() 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 // The following are a bunch of marker traits used to detect
@ -72,10 +61,6 @@ pub trait PyMappingDelItemProtocol<'p>: PyMappingProtocol<'p> + MutablePyClass {
type Result: IntoPyCallbackOutput<()>; type Result: IntoPyCallbackOutput<()>;
} }
pub trait PyMappingReversedProtocol<'p>: PyMappingProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>;
}
py_len_func!(len, PyMappingLenProtocol, Self::__len__); py_len_func!(len, PyMappingLenProtocol, Self::__len__);
py_binary_func!(getitem, PyMappingGetItemProtocol, Self::__getitem__); py_binary_func!(getitem, PyMappingGetItemProtocol, Self::__getitem__);
py_func_set!(setitem, PyMappingSetItemProtocol, Self::__setitem__); py_func_set!(setitem, PyMappingSetItemProtocol, Self::__setitem__);

View File

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

View File

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

View File

@ -283,16 +283,6 @@ pub trait PyNumberProtocol<'p>: PyClass {
{ {
unimplemented!() 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 fn __int__(&'p self) -> Self::Result
where where
Self: PyNumberIntProtocol<'p>, Self: PyNumberIntProtocol<'p>,
@ -311,16 +301,6 @@ pub trait PyNumberProtocol<'p>: PyClass {
{ {
unimplemented!() 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> { pub trait PyNumberAddProtocol<'p>: PyNumberProtocol<'p> {
@ -569,10 +549,6 @@ pub trait PyNumberInvertProtocol<'p>: PyNumberProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>; type Result: IntoPyCallbackOutput<PyObject>;
} }
pub trait PyNumberComplexProtocol<'p>: PyNumberProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>;
}
pub trait PyNumberIntProtocol<'p>: PyNumberProtocol<'p> { pub trait PyNumberIntProtocol<'p>: PyNumberProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>; type Result: IntoPyCallbackOutput<PyObject>;
} }
@ -581,11 +557,6 @@ pub trait PyNumberFloatProtocol<'p>: PyNumberProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>; type Result: IntoPyCallbackOutput<PyObject>;
} }
pub trait PyNumberRoundProtocol<'p>: PyNumberProtocol<'p> {
type NDigits: FromPyObject<'p>;
type Result: IntoPyCallbackOutput<PyObject>;
}
pub trait PyNumberIndexProtocol<'p>: PyNumberProtocol<'p> { pub trait PyNumberIndexProtocol<'p>: PyNumberProtocol<'p> {
type Result: IntoPyCallbackOutput<PyObject>; type Result: IntoPyCallbackOutput<PyObject>;
} }

View File

@ -37,33 +37,6 @@ pub trait PyAsyncProtocol<'p>: PyClass {
{ {
unimplemented!() 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> { pub trait PyAsyncAwaitProtocol<'p>: PyAsyncProtocol<'p> {
@ -81,17 +54,6 @@ pub trait PyAsyncAnextProtocol<'p>: PyAsyncProtocol<'p> {
type Result: IntoPyCallbackOutput<PyIterANextOutput>; 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!(await_, PyAsyncAwaitProtocol, Self::__await__);
py_unarys_func!(aiter, PyAsyncAiterProtocol, Self::__aiter__); py_unarys_func!(aiter, PyAsyncAiterProtocol, Self::__aiter__);
py_unarys_func!(anext, PyAsyncAnextProtocol, Self::__anext__); py_unarys_func!(anext, PyAsyncAnextProtocol, Self::__anext__);

View File

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

View File

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

View File

@ -2,7 +2,7 @@
use crate::panic::PanicException; use crate::panic::PanicException;
use crate::type_object::PyTypeObject; use crate::type_object::PyTypeObject;
use crate::types::PyType; use crate::types::{PyTraceback, PyType};
use crate::{ use crate::{
exceptions::{self, PyBaseException}, exceptions::{self, PyBaseException},
ffi, ffi,
@ -180,7 +180,7 @@ impl PyErr {
/// ///
/// Python::with_gil(|py| { /// Python::with_gil(|py| {
/// let err: PyErr = PyTypeError::new_err(("some type error",)); /// let err: PyErr = PyTypeError::new_err(("some type error",));
/// assert!(err.is_instance::<PyTypeError>(py)); /// assert!(err.is_instance_of::<PyTypeError>(py));
/// assert_eq!(err.pvalue(py).to_string(), "some type error"); /// assert_eq!(err.pvalue(py).to_string(), "some type error");
/// }); /// });
/// ``` /// ```
@ -201,7 +201,7 @@ impl PyErr {
/// assert_eq!(err.ptraceback(py), None); /// assert_eq!(err.ptraceback(py), None);
/// }); /// });
/// ``` /// ```
pub fn ptraceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyAny> { pub fn ptraceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyTraceback> {
self.normalized(py) self.normalized(py)
.ptraceback .ptraceback
.as_ref() .as_ref()
@ -366,13 +366,16 @@ impl PyErr {
} }
/// Returns true if the current exception is instance of `T`. /// Returns true if the current exception is instance of `T`.
pub fn is_instance<T>(&self, py: Python) -> bool pub fn is_instance(&self, py: Python, typ: &PyType) -> bool {
unsafe { ffi::PyErr_GivenExceptionMatches(self.ptype_ptr(py), typ.as_ptr()) != 0 }
}
/// Returns true if the current exception is instance of `T`.
pub fn is_instance_of<T>(&self, py: Python) -> bool
where where
T: PyTypeObject, T: PyTypeObject,
{ {
unsafe { self.is_instance(py, T::type_object(py))
ffi::PyErr_GivenExceptionMatches(self.ptype_ptr(py), T::type_object(py).as_ptr()) != 0
}
} }
/// Retrieves the exception instance for this error. /// Retrieves the exception instance for this error.
@ -497,7 +500,7 @@ impl PyErr {
*self_state = Some(PyErrState::Normalized(PyErrStateNormalized { *self_state = Some(PyErrState::Normalized(PyErrStateNormalized {
ptype: Py::from_owned_ptr_or_opt(py, ptype).expect("Exception type missing"), 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"), 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 { match self_state {
@ -612,11 +615,11 @@ mod tests {
fn set_valueerror() { fn set_valueerror() {
Python::with_gil(|py| { Python::with_gil(|py| {
let err: PyErr = exceptions::PyValueError::new_err("some exception message"); 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); err.restore(py);
assert!(PyErr::occurred(py)); assert!(PyErr::occurred(py));
let err = PyErr::fetch(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"); assert_eq!(err.to_string(), "ValueError: some exception message");
}) })
} }
@ -625,10 +628,10 @@ mod tests {
fn invalid_error_type() { fn invalid_error_type() {
Python::with_gil(|py| { Python::with_gil(|py| {
let err: PyErr = PyErr::new::<crate::types::PyString, _>(()); 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); err.restore(py);
let err = PyErr::fetch(py); let err = PyErr::fetch(py);
assert!(err.is_instance::<exceptions::PyTypeError>(py)); assert!(err.is_instance_of::<exceptions::PyTypeError>(py));
assert_eq!( assert_eq!(
err.to_string(), err.to_string(),
"TypeError: exceptions must derive from BaseException" "TypeError: exceptions must derive from BaseException"
@ -683,12 +686,7 @@ mod tests {
let mut fields = debug_str["PyErr { ".len()..debug_str.len() - 2].split(", "); let mut fields = debug_str["PyErr { ".len()..debug_str.len() - 2].split(", ");
assert_eq!(fields.next().unwrap(), "type: <class 'Exception'>"); assert_eq!(fields.next().unwrap(), "type: <class 'Exception'>");
if py.version_info() >= (3, 7) {
assert_eq!(fields.next().unwrap(), "value: Exception('banana')"); 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',)"));
}
let traceback = fields.next().unwrap(); let traceback = fields.next().unwrap();
assert!(traceback.starts_with("traceback: Some(<traceback object at 0x")); assert!(traceback.starts_with("traceback: Some(<traceback object at 0x"));

View File

@ -267,7 +267,7 @@ fn always_throws() -> PyResult<()> {
# Python::with_gil(|py| { # Python::with_gil(|py| {
# let fun = pyo3::wrap_pyfunction!(always_throws, py).unwrap(); # 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\"); # 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))
# }); # });
``` ```
@ -292,7 +292,7 @@ Python::with_gil(|py| {
let error_type = match result { let error_type = match result {
Ok(_) => \"Not an error\", 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\", Err(_) => \"Some other error\",
}; };
@ -611,7 +611,7 @@ macro_rules! test_exception {
.unwrap_or($exc_ty::new_err("a 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.instance(py).downcast().unwrap();
assert!(value.source().is_none()); assert!(value.source().is_none());
@ -619,7 +619,7 @@ macro_rules! test_exception {
err.set_cause(py, Some($crate::exceptions::PyValueError::new_err("a cause"))); err.set_cause(py, Some($crate::exceptions::PyValueError::new_err("a cause")));
assert!(value.source().is_some()); 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));
}) })
} }
}; };
@ -794,19 +794,11 @@ mod tests {
.into_instance(py) .into_instance(py)
.into_ref(py); .into_ref(py);
if py.version_info() >= (3, 7) {
assert_eq!(format!("{:?}", exc), "Exception('banana')"); assert_eq!(format!("{:?}", exc), "Exception('banana')");
} else {
assert_eq!(format!("{:?}", exc), "Exception('banana',)");
}
let source = exc.source().expect("cause should exist"); let source = exc.source().expect("cause should exist");
if py.version_info() >= (3, 7) {
assert_eq!(format!("{:?}", source), "TypeError('peach')"); assert_eq!(format!("{:?}", source), "TypeError('peach')");
} else {
assert_eq!(format!("{:?}", source), "TypeError('peach',)");
}
let source_source = source.source(); let source_source = source.source();
assert!(source_source.is_none(), "source_source should be None"); assert!(source_source.is_none(), "source_source should be None");

View File

@ -91,7 +91,7 @@ extern "C" {
pub fn PyObject_GetIter(arg1: *mut PyObject) -> *mut PyObject; 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. // non-limited PyTypeObject. Don't expose this since it cannot be used.
#[cfg(not(any(Py_LIMITED_API, PyPy)))] #[cfg(not(any(Py_LIMITED_API, PyPy)))]
#[inline] #[inline]
@ -156,7 +156,7 @@ extern "C" {
pub fn PyNumber_Or(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; 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. // non-limited PyTypeObject. Don't expose this since it cannot be used.
#[cfg(not(any(Py_LIMITED_API, PyPy)))] #[cfg(not(any(Py_LIMITED_API, PyPy)))]
#[inline] #[inline]

View File

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

View File

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

View File

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

View File

@ -361,7 +361,7 @@ pub struct PyDateTime_CAPI {
pub TimeType: *mut PyTypeObject, pub TimeType: *mut PyTypeObject,
pub DeltaType: *mut PyTypeObject, pub DeltaType: *mut PyTypeObject,
pub TZInfoType: *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 TimeZone_UTC: *mut PyObject,
pub Date_FromDate: unsafe extern "C" fn( pub Date_FromDate: unsafe extern "C" fn(
year: c_int, year: c_int,
@ -395,7 +395,7 @@ pub struct PyDateTime_CAPI {
normalize: c_int, normalize: c_int,
cls: *mut PyTypeObject, cls: *mut PyTypeObject,
) -> *mut PyObject, ) -> *mut PyObject,
#[cfg(all(Py_3_7, any(not(PyPy), Py_3_8)))] #[cfg(not(all(PyPy, not(Py_3_8))))]
pub TimeZone_FromTimeZone: pub TimeZone_FromTimeZone:
unsafe extern "C" fn(offset: *mut PyObject, name: *mut PyObject) -> *mut PyObject, 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 /// 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. /// 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 { pub static PyDateTime_TimeZone_UTC: _PyDateTime_TimeZone_UTC_impl = _PyDateTime_TimeZone_UTC_impl {
inner: &PyDateTimeAPI, inner: &PyDateTimeAPI,
}; };
@ -609,12 +609,12 @@ impl Deref for _PyDateTimeAPI_impl {
} }
#[doc(hidden)] #[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 { pub struct _PyDateTime_TimeZone_UTC_impl {
inner: &'static _PyDateTimeAPI_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 { impl Deref for _PyDateTime_TimeZone_UTC_impl {
type Target = crate::PyObject; type Target = crate::PyObject;
@ -661,7 +661,7 @@ mod tests {
} }
#[test] #[test]
#[cfg(all(Py_3_7, any(not(PyPy), Py_3_8)))] #[cfg(not(all(PyPy, not(Py_3_8))))]
fn test_utc_timezone() { fn test_utc_timezone() {
Python::with_gil(|py| { Python::with_gil(|py| {
let utc_timezone = PyDateTime_TimeZone_UTC.as_ref(py); let utc_timezone = PyDateTime_TimeZone_UTC.as_ref(py);

View File

@ -69,10 +69,7 @@ extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyImport_ReloadModule")] #[cfg_attr(PyPy, link_name = "PyPyImport_ReloadModule")]
pub fn PyImport_ReloadModule(m: *mut PyObject) -> *mut PyObject; pub fn PyImport_ReloadModule(m: *mut PyObject) -> *mut PyObject;
#[cfg(not(Py_3_9))] #[cfg(not(Py_3_9))]
#[deprecated( #[deprecated(note = "Removed in Python 3.9 as it was \"For internal use only\".")]
since = "0.14.0",
note = "Removed in Python 3.9 as it was \"For internal use only\"."
)]
pub fn PyImport_Cleanup(); pub fn PyImport_Cleanup();
pub fn PyImport_ImportFrozenModuleObject(name: *mut PyObject) -> c_int; pub fn PyImport_ImportFrozenModuleObject(name: *mut PyObject) -> c_int;
pub fn PyImport_ImportFrozenModule(name: *const c_char) -> 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")] #[cfg_attr(PyPy, link_name = "PyPyOS_InterruptOccurred")]
pub fn PyOS_InterruptOccurred() -> c_int; pub fn PyOS_InterruptOccurred() -> c_int;
#[cfg(not(Py_3_10))] #[cfg(not(Py_3_10))]
#[deprecated( #[deprecated(note = "Not documented in Python API; see Python 3.10 release notes")]
since = "0.14.0",
note = "Not documented in Python API; see Python 3.10 release notes"
)]
pub fn PyOS_InitInterrupts(); pub fn PyOS_InitInterrupts();
#[cfg(any(not(Py_LIMITED_API), Py_3_7))]
pub fn PyOS_BeforeFork(); pub fn PyOS_BeforeFork();
#[cfg(any(not(Py_LIMITED_API), Py_3_7))]
pub fn PyOS_AfterFork_Parent(); pub fn PyOS_AfterFork_Parent();
#[cfg(any(not(Py_LIMITED_API), Py_3_7))]
pub fn PyOS_AfterFork_Child(); 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")] #[cfg_attr(PyPy, link_name = "PyPyOS_AfterFork")]
pub fn PyOS_AfterFork(); pub fn PyOS_AfterFork();

View File

@ -31,7 +31,7 @@ pub unsafe fn PyCFunction_Check(op: *mut PyObject) -> c_int {
pub type PyCFunction = pub type PyCFunction =
unsafe extern "C" fn(slf: *mut PyObject, args: *mut PyObject) -> *mut PyObject; 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( pub type _PyCFunctionFast = unsafe extern "C" fn(
slf: *mut PyObject, slf: *mut PyObject,
args: *mut *mut PyObject, args: *mut *mut PyObject,
@ -45,7 +45,7 @@ pub type PyCFunctionWithKeywords = unsafe extern "C" fn(
kwds: *mut PyObject, kwds: *mut PyObject,
) -> *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( pub type _PyCFunctionFastWithKeywords = unsafe extern "C" fn(
slf: *mut PyObject, slf: *mut PyObject,
args: *const *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 /* METH_FASTCALL indicates the PEP 590 Vectorcall calling format. It may
be specified alone or with METH_KEYWORDS. */ 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; pub const METH_FASTCALL: c_int = 0x0080;
// skipped METH_STACKLESS // skipped METH_STACKLESS

View File

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

View File

@ -52,20 +52,8 @@ extern "C" {
stop: *mut Py_ssize_t, stop: *mut Py_ssize_t,
step: *mut Py_ssize_t, step: *mut Py_ssize_t,
) -> c_int; ) -> 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] #[inline]
pub unsafe fn PySlice_GetIndicesEx( pub unsafe fn PySlice_GetIndicesEx(
slice: *mut PyObject, slice: *mut PyObject,

View File

@ -165,12 +165,9 @@ extern "C" {
) -> *mut PyObject; ) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8String")] #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8String")]
pub fn PyUnicode_AsUTF8String(unicode: *mut PyObject) -> *mut PyObject; 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")] #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8AndSize")]
pub fn PyUnicode_AsUTF8AndSize(unicode: *mut PyObject, size: *mut Py_ssize_t) -> *const c_char; 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")] #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeUTF32")]
pub fn PyUnicode_DecodeUTF32( pub fn PyUnicode_DecodeUTF32(
string: *const c_char, 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 /// signal handling depends on the notion of a 'main thread', which must be the thread that
/// initializes the Python interpreter. /// initializes the Python interpreter.
/// ///
/// If both the Python interpreter and Python threading are already initialized, this function has /// If the Python interpreter is already initialized, this function has no effect.
/// no effect.
/// ///
/// This function is unavailable under PyPy because PyPy cannot be embedded in Rust (or any other /// This function is unavailable under PyPy because PyPy cannot be embedded in Rust (or any other
/// software). Support for this is tracked on the /// software). Support for this is tracked on the
/// [PyPy issue tracker](https://foss.heptapod.net/pypy/pypy/-/issues/3286). /// [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 /// # Examples
/// ```rust /// ```rust
/// use pyo3::prelude::*; /// use pyo3::prelude::*;
@ -69,33 +61,13 @@ pub(crate) fn gil_is_acquired() -> bool {
/// # } /// # }
/// ``` /// ```
#[cfg(not(PyPy))] #[cfg(not(PyPy))]
#[allow(clippy::collapsible_if)] // for if cfg!
pub fn prepare_freethreaded_python() { pub fn prepare_freethreaded_python() {
// Protect against race conditions when Python is not yet initialized and multiple threads // 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 // 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. // concurrent initialization of the Python runtime by other users of the Python C API.
START.call_once_force(|_| unsafe { 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. // Use call_once_force because if initialization panics, it's okay to try again.
if ffi::Py_IsInitialized() != 0 { 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.
ffi::Py_InitializeEx(0); ffi::Py_InitializeEx(0);
// Release the GIL. // Release the GIL.
@ -134,7 +106,6 @@ pub fn prepare_freethreaded_python() {
/// # } /// # }
/// ``` /// ```
#[cfg(not(PyPy))] #[cfg(not(PyPy))]
#[allow(clippy::collapsible_if)] // for if cfg!
pub unsafe fn with_embedded_python_interpreter<F, R>(f: F) -> R pub unsafe fn with_embedded_python_interpreter<F, R>(f: F) -> R
where where
F: for<'p> FnOnce(Python<'p>) -> R, F: for<'p> FnOnce(Python<'p>) -> R,
@ -147,15 +118,7 @@ where
ffi::Py_InitializeEx(0); ffi::Py_InitializeEx(0);
// Changed in version 3.7: This function is now called by Py_Initialize(), so you dont have to // Safety: the GIL is already held because of the Py_IntializeEx call.
// 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.
let pool = GILPool::new(); let pool = GILPool::new();
// Import the threading module - this ensures that it will associate this thread as the "main" // Import the threading module - this ensures that it will associate this thread as the "main"
@ -230,14 +193,6 @@ impl GILGuard {
Consider calling `pyo3::prepare_freethreaded_python()` before attempting \ Consider calling `pyo3::prepare_freethreaded_python()` before attempting \
to use Python APIs." 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. //! 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]`")] #[deprecated(since = "0.15.0", note = "use `fn __call__` instead of `#[call]`")]
pub const CALL_ATTRIBUTE: () = (); pub const CALL_ATTRIBUTE: () = ();

View File

@ -534,6 +534,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. /// Calls the object.
/// ///
/// This is equivalent to the Python expression `self(*args, **kwargs)`. /// This is equivalent to the Python expression `self(*args, **kwargs)`.

View File

@ -1,5 +1,5 @@
#![cfg_attr(feature = "nightly", feature(specialization))] #![cfg_attr(feature = "nightly", feature(specialization))]
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![cfg_attr( #![cfg_attr(
docsrs, // rustdoc:: is not supported on msrv docsrs, // rustdoc:: is not supported on msrv
deny( deny(
@ -102,7 +102,7 @@
//! PyO3 uses `rustc`'s `--cfg` flags to enable or disable code used for different Python versions. //! 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. //! 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. //! compiling for a given minimum Python version.
//! - `Py_LIMITED_API`: Marks code enabled when the `abi3` feature flag is enabled. //! - `Py_LIMITED_API`: Marks code enabled when the `abi3` feature flag is enabled.
//! - `PyPy` - Marks code enabled when compiling for PyPy. //! - `PyPy` - Marks code enabled when compiling for PyPy.
@ -110,8 +110,8 @@
//! # Minimum supported Rust and Python versions //! # Minimum supported Rust and Python versions
//! //!
//! PyO3 supports the following software versions: //! PyO3 supports the following software versions:
//! - Python 3.6 and up (CPython and PyPy) //! - Python 3.7 and up (CPython and PyPy)
//! - Rust 1.41 and up //! - Rust 1.48 and up
//! //!
//! # Example: Building a native Python module //! # Example: Building a native Python module
//! //!
@ -405,6 +405,10 @@ pub mod doc_test {
doctest!("guide/src/trait_bounds.md", guide_trait_bounds_md); doctest!("guide/src/trait_bounds.md", guide_trait_bounds_md);
doctest!("guide/src/types.md", guide_types_md); doctest!("guide/src/types.md", guide_types_md);
doctest!("guide/src/faq.md", faq); doctest!("guide/src/faq.md", faq);
doctest!(
"guide/src/python_typing_hints.md",
guide_python_typing_hints
);
// deliberate choice not to test guide/ecosystem because those pages depend on external crates // deliberate choice not to test guide/ecosystem because those pages depend on external crates
// such as pyo3_asyncio. // such as pyo3_asyncio.

View File

@ -86,7 +86,8 @@ where
slots.push(ffi::Py_tp_free, free as _); slots.push(ffi::Py_tp_free, free as _);
} }
if cfg!(Py_3_9) { #[cfg(Py_3_9)]
{
let members = py_class_members::<T>(); let members = py_class_members::<T>();
if !members.is_empty() { if !members.is_empty() {
slots.push(ffi::Py_tp_members, into_raw(members)) slots.push(ffi::Py_tp_members, into_raw(members))
@ -158,7 +159,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 // Setting buffer protocols via slots doesn't work until Python 3.9, so on older versions we
// must manually fixup the type object. // must manually fixup the type object.
if cfg!(not(Py_3_9)) { #[cfg(not(Py_3_9))]
{
if let Some(buffer) = T::get_buffer() { if let Some(buffer) = T::get_buffer() {
unsafe { unsafe {
(*(*type_object).tp_as_buffer).bf_getbuffer = buffer.bf_getbuffer; (*(*type_object).tp_as_buffer).bf_getbuffer = buffer.bf_getbuffer;
@ -169,7 +171,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 // 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. // older versions again we must fixup the type object.
if cfg!(not(Py_3_9)) { #[cfg(not(Py_3_9))]
{
// __dict__ support // __dict__ support
if let Some(dict_offset) = PyCell::<T>::dict_offset() { if let Some(dict_offset) = PyCell::<T>::dict_offset() {
unsafe { unsafe {
@ -261,12 +264,6 @@ fn py_class_members<T: PyClass>() -> Vec<ffi::structmember::PyMemberDef> {
members 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 { const PY_GET_SET_DEF_INIT: ffi::PyGetSetDef = ffi::PyGetSetDef {
name: ptr::null_mut(), name: ptr::null_mut(),
get: None, get: None,
@ -275,7 +272,6 @@ const PY_GET_SET_DEF_INIT: ffi::PyGetSetDef = ffi::PyGetSetDef {
closure: ptr::null_mut(), closure: ptr::null_mut(),
}; };
#[allow(clippy::collapsible_if)] // for if cfg!
fn py_class_properties( fn py_class_properties(
is_dummy: bool, is_dummy: bool,
for_each_method_def: &dyn Fn(&mut dyn FnMut(&[PyMethodDefType])), for_each_method_def: &dyn Fn(&mut dyn FnMut(&[PyMethodDefType])),

View File

@ -367,26 +367,30 @@ impl<'py> Python<'py> {
F: Send + FnOnce() -> T, F: Send + FnOnce() -> T,
T: Send, 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 // The `Send` bound on the closure prevents the user from
// transferring the `Python` token into the closure. // transferring the `Python` token into the closure.
let count = gil::GIL_COUNT.with(|c| c.replace(0)); let count = gil::GIL_COUNT.with(|c| c.replace(0));
let tstate = unsafe { ffi::PyEval_SaveThread() }; 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 let _guard = RestoreGuard { count, tstate };
gil::GIL_COUNT.with(|c| c.set(count)); f()
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))
} }
/// Evaluates a Python expression in the given context and returns the result. /// Evaluates a Python expression in the given context and returns the result.
@ -546,9 +550,9 @@ impl<'py> Python<'py> {
/// ```rust /// ```rust
/// # use pyo3::Python; /// # use pyo3::Python;
/// Python::with_gil(|py| { /// Python::with_gil(|py| {
/// // PyO3 supports Python 3.6 and up. /// // PyO3 supports Python 3.7 and up.
/// assert!(py.version_info() >= (3, 6)); /// assert!(py.version_info() >= (3, 7));
/// assert!(py.version_info() >= (3, 6, 0)); /// assert!(py.version_info() >= (3, 7, 0));
/// }); /// });
/// ``` /// ```
pub fn version_info(self) -> PythonVersionInfo<'py> { 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] #[test]
fn test_allow_threads_panics_safely() { fn test_allow_threads_panics_safely() {
Python::with_gil(|py| { Python::with_gil(|py| {
@ -864,10 +891,6 @@ mod tests {
fn test_python_version_info() { fn test_python_version_info() {
Python::with_gil(|py| { Python::with_gil(|py| {
let version = py.version_info(); 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)] #[cfg(Py_3_7)]
assert!(version >= (3, 7)); assert!(version >= (3, 7));
#[cfg(Py_3_7)] #[cfg(Py_3_7)]

View File

@ -74,7 +74,7 @@ impl PyAny {
/// ///
/// Python::with_gil(|py| { /// Python::with_gil(|py| {
/// let dict = PyDict::new(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(); /// let any: &PyAny = dict.as_ref();
/// assert!(any.downcast::<PyDict>().is_ok()); /// assert!(any.downcast::<PyDict>().is_ok());
/// assert!(any.downcast::<PyList>().is_err()); /// assert!(any.downcast::<PyList>().is_err());
@ -665,11 +665,21 @@ impl PyAny {
unsafe { self.py().from_owned_ptr(ffi::PyObject_Dir(self.as_ptr())) } 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`. /// Checks whether this object is an instance of type `T`.
/// ///
/// This is equivalent to the Python expression `isinstance(self, T)`. /// This is equivalent to the Python expression `isinstance(self, T)`,
pub fn is_instance<T: PyTypeObject>(&self) -> PyResult<bool> { /// if the type `T` is known at compile time.
T::type_object(self.py()).is_instance(self) 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. /// Returns a GIL marker constrained to the lifetime of this type.
@ -682,6 +692,7 @@ impl PyAny {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
type_object::PyTypeObject,
types::{IntoPyDict, PyList, PyLong, PyModule}, types::{IntoPyDict, PyList, PyLong, PyModule},
Python, ToPyObject, Python, ToPyObject,
}; };
@ -776,10 +787,18 @@ mod tests {
fn test_any_isinstance() { fn test_any_isinstance() {
Python::with_gil(|py| { Python::with_gil(|py| {
let x = 5.to_object(py).into_ref(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); 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() { fn test_from_err() {
Python::with_gil(|py| { Python::with_gil(|py| {
if let Err(err) = PyByteArray::from(py, &py.None()) { 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 { } else {
panic!("error"); panic!("error");
} }
@ -293,7 +293,7 @@ mod tests {
assert!(py_bytearray_result assert!(py_bytearray_result
.err() .err()
.unwrap() .unwrap()
.is_instance::<PyValueError>(py)); .is_instance_of::<PyValueError>(py));
}) })
} }
} }

View File

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

View File

@ -52,12 +52,14 @@ impl<'source> FromPyObject<'source> for f64 {
fn extract(obj: &'source PyAny) -> PyResult<Self> { fn extract(obj: &'source PyAny) -> PyResult<Self> {
let v = unsafe { ffi::PyFloat_AsDouble(obj.as_ptr()) }; let v = unsafe { ffi::PyFloat_AsDouble(obj.as_ptr()) };
if v == -1.0 && PyErr::occurred(obj.py()) { if v == -1.0 {
Err(PyErr::fetch(obj.py())) if let Some(err) = PyErr::take(obj.py()) {
} else { return Err(err);
Ok(v)
} }
} }
Ok(v)
}
} }
impl ToPyObject for f32 { impl ToPyObject for f32 {

View File

@ -2,7 +2,7 @@
// //
// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython // based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython
use crate::{ffi, AsPyPointer, PyAny, PyErr, PyResult, Python}; use crate::{ffi, AsPyPointer, IntoPyPointer, Py, PyAny, PyErr, PyNativeType, PyResult, Python};
#[cfg(any(not(Py_LIMITED_API), Py_3_8))] #[cfg(any(not(Py_LIMITED_API), Py_3_8))]
use crate::{PyDowncastError, PyTryFrom}; use crate::{PyDowncastError, PyTryFrom};
@ -58,13 +58,7 @@ impl<'p> Iterator for &'p PyIterator {
match unsafe { py.from_owned_ptr_or_opt(ffi::PyIter_Next(self.0.as_ptr())) } { match unsafe { py.from_owned_ptr_or_opt(ffi::PyIter_Next(self.0.as_ptr())) } {
Some(obj) => Some(Ok(obj)), Some(obj) => Some(Ok(obj)),
None => { None => PyErr::take(py).map(Err),
if PyErr::occurred(py) {
Some(Err(PyErr::fetch(py)))
} else {
None
}
}
} }
} }
} }
@ -94,6 +88,22 @@ impl<'v> PyTryFrom<'v> for PyIterator {
} }
} }
impl Py<PyIterator> {
/// Borrows a GIL-bound reference to the PyIterator. By binding to the GIL lifetime, this
/// allows the GIL-bound reference to not require `Python` for any of its methods.
pub fn as_ref<'py>(&'py self, _py: Python<'py>) -> &'py PyIterator {
let any = self.as_ptr() as *const PyAny;
unsafe { PyNativeType::unchecked_downcast(&*any) }
}
/// Similar to [`as_ref`](#method.as_ref), and also consumes this `Py` and registers the
/// Python object reference in PyO3's object storage. The reference count for the Python
/// object will not be decreased until the GIL lifetime ends.
pub fn into_ref(self, py: Python) -> &PyIterator {
unsafe { py.from_owned_ptr(self.into_ptr()) }
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::PyIterator; use super::PyIterator;
@ -101,8 +111,8 @@ mod tests {
use crate::gil::GILPool; use crate::gil::GILPool;
use crate::types::{PyDict, PyList}; use crate::types::{PyDict, PyList};
#[cfg(any(not(Py_LIMITED_API), Py_3_8))] #[cfg(any(not(Py_LIMITED_API), Py_3_8))]
use crate::{Py, PyAny, PyTryFrom}; use crate::PyTryFrom;
use crate::{Python, ToPyObject}; use crate::{Py, PyAny, Python, ToPyObject};
use indoc::indoc; use indoc::indoc;
#[test] #[test]
@ -196,7 +206,7 @@ mod tests {
let x = 5.to_object(py); let x = 5.to_object(py);
let err = PyIterator::from_object(py, &x).unwrap_err(); let err = PyIterator::from_object(py, &x).unwrap_err();
assert!(err.is_instance::<PyTypeError>(py)); assert!(err.is_instance_of::<PyTypeError>(py));
}); });
} }
@ -209,4 +219,26 @@ mod tests {
assert_eq!(obj, iter.into()); assert_eq!(obj, iter.into());
}); });
} }
#[test]
fn test_as_ref() {
Python::with_gil(|py| {
let iter: Py<PyIterator> = PyAny::iter(PyList::empty(py)).unwrap().into();
let mut iter_ref: &PyIterator = iter.as_ref(py);
assert!(iter_ref.next().is_none());
})
}
#[test]
fn test_into_ref() {
Python::with_gil(|py| {
let bare_iter = PyAny::iter(PyList::empty(py)).unwrap();
assert_eq!(bare_iter.get_refcnt(), 1);
let iter: Py<PyIterator> = bare_iter.into();
assert_eq!(bare_iter.get_refcnt(), 2);
let mut iter_ref = iter.into_ref(py);
assert!(iter_ref.next().is_none());
assert_eq!(iter_ref.get_refcnt(), 2);
})
}
} }

View File

@ -2,9 +2,10 @@
use crate::err::{PyDowncastError, PyErr, PyResult}; use crate::err::{PyDowncastError, PyErr, PyResult};
use crate::types::{PyAny, PySequence}; use crate::types::{PyAny, PySequence};
use crate::AsPyPointer; use crate::{
use crate::{ffi, ToPyObject}; ffi, AsPyPointer, IntoPyPointer, Py, PyNativeType, PyTryFrom, Python, ToBorrowedObject,
use crate::{PyTryFrom, ToBorrowedObject}; ToPyObject,
};
/// Represents a reference to a Python object supporting the mapping protocol. /// Represents a reference to a Python object supporting the mapping protocol.
#[repr(transparent)] #[repr(transparent)]
@ -120,11 +121,31 @@ impl<'v> PyTryFrom<'v> for PyMapping {
} }
} }
impl Py<PyMapping> {
/// Borrows a GIL-bound reference to the PyMapping. By binding to the GIL lifetime, this
/// allows the GIL-bound reference to not require `Python` for any of its methods.
pub fn as_ref<'py>(&'py self, _py: Python<'py>) -> &'py PyMapping {
let any = self.as_ptr() as *const PyAny;
unsafe { PyNativeType::unchecked_downcast(&*any) }
}
/// Similar to [`as_ref`](#method.as_ref), and also consumes this `Py` and registers the
/// Python object reference in PyO3's object storage. The reference count for the Python
/// object will not be decreased until the GIL lifetime ends.
pub fn into_ref(self, py: Python) -> &PyMapping {
unsafe { py.from_owned_ptr(self.into_ptr()) }
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::collections::HashMap; use std::collections::HashMap;
use crate::{exceptions::PyKeyError, types::PyTuple, Python}; use crate::{
exceptions::PyKeyError,
types::{PyDict, PyTuple},
Python,
};
use super::*; use super::*;
@ -159,7 +180,7 @@ mod tests {
assert!(mapping assert!(mapping
.get_item(8i32) .get_item(8i32)
.unwrap_err() .unwrap_err()
.is_instance::<PyKeyError>(py)); .is_instance_of::<PyKeyError>(py));
}); });
} }
@ -195,7 +216,7 @@ mod tests {
assert!(mapping assert!(mapping
.get_item(7i32) .get_item(7i32)
.unwrap_err() .unwrap_err()
.is_instance::<PyKeyError>(py)); .is_instance_of::<PyKeyError>(py));
}); });
} }
@ -256,4 +277,26 @@ mod tests {
assert_eq!(32 + 42 + 123, values_sum); assert_eq!(32 + 42 + 123, values_sum);
}); });
} }
#[test]
fn test_as_ref() {
Python::with_gil(|py| {
let mapping: Py<PyMapping> = PyDict::new(py).as_mapping().into();
let mapping_ref: &PyMapping = mapping.as_ref(py);
assert_eq!(mapping_ref.len().unwrap(), 0);
})
}
#[test]
fn test_into_ref() {
Python::with_gil(|py| {
let bare_mapping = PyDict::new(py).as_mapping();
assert_eq!(bare_mapping.get_refcnt(), 1);
let mapping: Py<PyMapping> = bare_mapping.into();
assert_eq!(bare_mapping.get_refcnt(), 2);
let mapping_ref = mapping.into_ref(py);
assert_eq!(mapping_ref.len().unwrap(), 0);
assert_eq!(mapping_ref.get_refcnt(), 2);
})
}
} }

View File

@ -26,6 +26,7 @@ pub use self::slice::{PySlice, PySliceIndices};
#[cfg(all(not(Py_LIMITED_API), target_endian = "little"))] #[cfg(all(not(Py_LIMITED_API), target_endian = "little"))]
pub use self::string::PyStringData; pub use self::string::PyStringData;
pub use self::string::{PyString, PyString as PyUnicode}; pub use self::string::{PyString, PyString as PyUnicode};
pub use self::traceback::PyTraceback;
pub use self::tuple::PyTuple; pub use self::tuple::PyTuple;
pub use self::typeobject::PyType; pub use self::typeobject::PyType;
@ -237,5 +238,6 @@ mod sequence;
mod set; mod set;
mod slice; mod slice;
mod string; mod string;
mod traceback;
mod tuple; mod tuple;
mod typeobject; mod typeobject;

View File

@ -8,9 +8,9 @@ use crate::exceptions;
use crate::ffi; use crate::ffi;
use crate::pyclass::PyClass; use crate::pyclass::PyClass;
use crate::type_object::PyTypeObject; use crate::type_object::PyTypeObject;
use crate::types::PyCFunction;
use crate::types::{PyAny, PyDict, PyList}; use crate::types::{PyAny, PyDict, PyList};
use crate::types::{PyCFunction, PyTuple}; use crate::{AsPyPointer, IntoPy, PyObject, Python};
use crate::{AsPyPointer, IntoPy, Py, PyObject, Python};
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::str; use std::str;
@ -146,7 +146,7 @@ impl PyModule {
match self.getattr("__all__") { match self.getattr("__all__") {
Ok(idx) => idx.downcast().map_err(PyErr::from), Ok(idx) => idx.downcast().map_err(PyErr::from),
Err(err) => { 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()); let l = PyList::empty(self.py());
self.setattr("__all__", l).map_err(PyErr::from)?; self.setattr("__all__", l).map_err(PyErr::from)?;
Ok(l) Ok(l)
@ -367,46 +367,6 @@ impl PyModule {
let name = fun.getattr("__name__")?.extract()?; let name = fun.getattr("__name__")?.extract()?;
self.add(name, fun) 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)] #[cfg(test)]

View File

@ -10,18 +10,6 @@ use std::convert::TryFrom;
use std::i64; use std::i64;
use std::os::raw::c_long; use std::os::raw::c_long;
fn err_if_invalid_value<T: PartialEq>(
py: Python,
invalid_value: T,
actual_value: T,
) -> PyResult<T> {
if actual_value == invalid_value && PyErr::occurred(py) {
Err(PyErr::fetch(py))
} else {
Ok(actual_value)
}
}
macro_rules! int_fits_larger_int { macro_rules! int_fits_larger_int {
($rust_type:ty, $larger_type:ty) => { ($rust_type:ty, $larger_type:ty) => {
impl ToPyObject for $rust_type { impl ToPyObject for $rust_type {
@ -273,6 +261,20 @@ mod slow_128bit_int_conversion {
int_convert_128!(u128, u64); int_convert_128!(u128, u64);
} }
fn err_if_invalid_value<T: PartialEq>(
py: Python,
invalid_value: T,
actual_value: T,
) -> PyResult<T> {
if actual_value == invalid_value {
if let Some(err) = PyErr::take(py) {
return Err(err);
}
}
Ok(actual_value)
}
#[cfg(test)] #[cfg(test)]
mod test_128bit_intergers { mod test_128bit_intergers {
use super::*; use super::*;
@ -343,7 +345,7 @@ mod test_128bit_intergers {
Python::with_gil(|py| { Python::with_gil(|py| {
let obj = py.eval("(1 << 130) * -1", None, None).unwrap(); let obj = py.eval("(1 << 130) * -1", None, None).unwrap();
let err = obj.extract::<i128>().unwrap_err(); let err = obj.extract::<i128>().unwrap_err();
assert!(err.is_instance::<crate::exceptions::PyOverflowError>(py)); assert!(err.is_instance_of::<crate::exceptions::PyOverflowError>(py));
}) })
} }
@ -352,7 +354,7 @@ mod test_128bit_intergers {
Python::with_gil(|py| { Python::with_gil(|py| {
let obj = py.eval("1 << 130", None, None).unwrap(); let obj = py.eval("1 << 130", None, None).unwrap();
let err = obj.extract::<u128>().unwrap_err(); let err = obj.extract::<u128>().unwrap_err();
assert!(err.is_instance::<crate::exceptions::PyOverflowError>(py)); assert!(err.is_instance_of::<crate::exceptions::PyOverflowError>(py));
}) })
} }
} }
@ -419,7 +421,7 @@ mod tests {
let obj = ("123").to_object(py); let obj = ("123").to_object(py);
let err = obj.extract::<$t>(py).unwrap_err(); let err = obj.extract::<$t>(py).unwrap_err();
assert!(err.is_instance::<exceptions::PyTypeError>(py)); assert!(err.is_instance_of::<exceptions::PyTypeError>(py));
}); });
} }
@ -429,7 +431,7 @@ mod tests {
let obj = (12.3).to_object(py); let obj = (12.3).to_object(py);
let err = obj.extract::<$t>(py).unwrap_err(); let err = obj.extract::<$t>(py).unwrap_err();
assert!(err.is_instance::<exceptions::PyTypeError>(py));}); assert!(err.is_instance_of::<exceptions::PyTypeError>(py));});
} }
#[test] #[test]

View File

@ -1,10 +1,10 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::err::{self, PyDowncastError, PyErr, PyResult};
use crate::ffi;
use crate::internal_tricks::get_ssize_index; use crate::internal_tricks::get_ssize_index;
use crate::types::{PyAny, PyList, PyTuple}; use crate::types::{PyAny, PyList, PyTuple};
use crate::AsPyPointer; use crate::{ffi, PyNativeType};
use crate::{AsPyPointer, IntoPyPointer, Py, Python};
use crate::{FromPyObject, PyTryFrom, ToBorrowedObject}; use crate::{FromPyObject, PyTryFrom, ToBorrowedObject};
/// Represents a reference to a Python object supporting the sequence protocol. /// Represents a reference to a Python object supporting the sequence protocol.
@ -335,12 +335,36 @@ impl<'v> PyTryFrom<'v> for PySequence {
} }
} }
impl Py<PySequence> {
/// Borrows a GIL-bound reference to the PySequence. By binding to the GIL lifetime, this
/// allows the GIL-bound reference to not require `Python` for any of its methods.
///
/// ```
/// # use pyo3::prelude::*;
/// # use pyo3::types::{PyList, PySequence};
/// # Python::with_gil(|py| {
/// let seq: Py<PySequence> = PyList::empty(py).as_sequence().into();
/// let seq: &PySequence = seq.as_ref(py);
/// assert_eq!(seq.len().unwrap(), 0);
/// # });
/// ```
pub fn as_ref<'py>(&'py self, _py: Python<'py>) -> &'py PySequence {
let any = self.as_ptr() as *const PyAny;
unsafe { PyNativeType::unchecked_downcast(&*any) }
}
/// Similar to [`as_ref`](#method.as_ref), and also consumes this `Py` and registers the
/// Python object reference in PyO3's object storage. The reference count for the Python
/// object will not be decreased until the GIL lifetime ends.
pub fn into_ref(self, py: Python) -> &PySequence {
unsafe { py.from_owned_ptr(self.into_ptr()) }
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::types::{PyList, PySequence}; use crate::types::{PyList, PySequence};
use crate::AsPyPointer; use crate::{AsPyPointer, Py, PyObject, PyTryFrom, Python, ToPyObject};
use crate::Python;
use crate::{PyObject, PyTryFrom, ToPyObject};
fn get_object() -> PyObject { fn get_object() -> PyObject {
// Convenience function for getting a single unique object // Convenience function for getting a single unique object
@ -818,4 +842,26 @@ mod tests {
assert!(seq_from.list().is_ok()); assert!(seq_from.list().is_ok());
}); });
} }
#[test]
fn test_as_ref() {
Python::with_gil(|py| {
let seq: Py<PySequence> = PyList::empty(py).as_sequence().into();
let seq_ref: &PySequence = seq.as_ref(py);
assert_eq!(seq_ref.len().unwrap(), 0);
})
}
#[test]
fn test_into_ref() {
Python::with_gil(|py| {
let bare_seq = PyList::empty(py).as_sequence();
assert_eq!(bare_seq.get_refcnt(), 1);
let seq: Py<PySequence> = bare_seq.into();
assert_eq!(bare_seq.get_refcnt(), 2);
let seq_ref = seq.into_ref(py);
assert_eq!(seq_ref.len().unwrap(), 0);
assert_eq!(seq_ref.get_refcnt(), 2);
})
}
} }

View File

@ -227,8 +227,9 @@ impl PyString {
pub unsafe fn data(&self) -> PyResult<PyStringData<'_>> { pub unsafe fn data(&self) -> PyResult<PyStringData<'_>> {
let ptr = self.as_ptr(); let ptr = self.as_ptr();
if cfg!(not(Py_3_12)) { #[cfg(not(Py_3_12))]
#[allow(deprecated)] #[allow(deprecated)]
{
let ready = ffi::PyUnicode_READY(ptr); let ready = ffi::PyUnicode_READY(ptr);
if ready != 0 { if ready != 0 {
// Exception was created on failure. // Exception was created on failure.

82
src/types/traceback.rs Normal file
View File

@ -0,0 +1,82 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::err::{error_on_minusone, PyResult};
use crate::ffi;
use crate::types::PyString;
use crate::{AsPyPointer, PyAny};
/// Represents a Python traceback.
#[repr(transparent)]
pub struct PyTraceback(PyAny);
pyobject_native_type_core!(
PyTraceback,
ffi::PyTraceBack_Type,
#checkfunction=ffi::PyTraceBack_Check
);
impl PyTraceback {
/// Formats the traceback as a string.
///
/// This does not include the exception type and value. The exception type and value can be
/// formatted using the `Display` implementation for `PyErr`.
///
/// # Example
///
/// The following code formats a Python traceback and exception pair from Rust:
///
/// ```rust
/// # use pyo3::{Python, PyResult};
/// # let result: PyResult<()> =
/// Python::with_gil(|py| {
/// let err = py
/// .run("raise Exception('banana')", None, None)
/// .expect_err("raise will create a Python error");
///
/// let traceback = err.ptraceback(py).expect("raised exception will have a traceback");
/// assert_eq!(
/// format!("{}{}", traceback.format()?, err),
/// "\
/// Traceback (most recent call last):
/// File \"<string>\", line 1, in <module>
/// Exception: banana\
/// "
/// );
/// Ok(())
/// })
/// # ;
/// # result.expect("example failed");
/// ```
pub fn format(&self) -> PyResult<String> {
let py = self.py();
let string_io = py.import("io")?.getattr("StringIO")?.call0()?;
let result = unsafe { ffi::PyTraceBack_Print(self.as_ptr(), string_io.as_ptr()) };
error_on_minusone(py, result)?;
let formatted = string_io
.getattr("getvalue")?
.call0()?
.downcast::<PyString>()?
.to_str()?
.to_owned();
Ok(formatted)
}
}
#[cfg(test)]
mod tests {
use crate::Python;
#[test]
fn format_traceback() {
Python::with_gil(|py| {
let err = py
.run("raise Exception('banana')", None, None)
.expect_err("raising should have given us an error");
assert_eq!(
err.ptraceback(py).unwrap().format().unwrap(),
"Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\n"
);
})
}
}

View File

@ -40,25 +40,48 @@ impl PyType {
self.getattr("__qualname__")?.extract() self.getattr("__qualname__")?.extract()
} }
/// Checks whether `self` is subclass of type `T`. /// Checks whether `self` is a subclass of `other`.
/// ///
/// Equivalent to Python's `issubclass` function. /// Equivalent to the Python expression `issubclass(self, other)`.
pub fn is_subclass<T>(&self) -> PyResult<bool> pub fn is_subclass(&self, other: &PyType) -> PyResult<bool> {
where let result = unsafe { ffi::PyObject_IsSubclass(self.as_ptr(), other.as_ptr()) };
T: PyTypeObject,
{
let result =
unsafe { ffi::PyObject_IsSubclass(self.as_ptr(), T::type_object(self.py()).as_ptr()) };
err::error_on_minusone(self.py(), result)?; err::error_on_minusone(self.py(), result)?;
Ok(result == 1) Ok(result == 1)
} }
/// Check whether `obj` is an instance of `self`. /// Checks whether `self` is a subclass of type `T`.
/// ///
/// Equivalent to Python's `isinstance` function. /// Equivalent to the Python expression `issubclass(self, T)`, if the type
pub fn is_instance<T: AsPyPointer>(&self, obj: &T) -> PyResult<bool> { /// `T` is known at compile time.
let result = unsafe { ffi::PyObject_IsInstance(obj.as_ptr(), self.as_ptr()) }; pub fn is_subclass_of<T>(&self) -> PyResult<bool>
err::error_on_minusone(self.py(), result)?; where
Ok(result == 1) T: PyTypeObject,
{
self.is_subclass(T::type_object(self.py()))
}
}
#[cfg(test)]
mod tests {
use crate::{
type_object::PyTypeObject,
types::{PyBool, PyLong},
Python,
};
#[test]
fn test_type_is_subclass() {
Python::with_gil(|py| {
let bool_type = PyBool::type_object(py);
let long_type = PyLong::type_object(py);
assert!(bool_type.is_subclass(long_type).unwrap());
});
}
#[test]
fn test_type_is_subclass_of() {
Python::with_gil(|py| {
assert!(PyBool::type_object(py).is_subclass_of::<PyLong>().unwrap());
});
} }
} }

View File

@ -38,10 +38,6 @@ impl PyNumberProtocol for UnaryArithmetic {
fn __abs__(&self) -> Self { fn __abs__(&self) -> Self {
Self::new(self.inner.abs()) Self::new(self.inner.abs())
} }
fn __round__(&self, _ndigits: Option<u32>) -> Self {
Self::new(self.inner.round())
}
} }
#[test] #[test]
@ -53,8 +49,6 @@ fn unary_arithmetic() {
py_run!(py, c, "assert repr(-c) == 'UA(-2.7)'"); py_run!(py, c, "assert repr(-c) == 'UA(-2.7)'");
py_run!(py, c, "assert repr(+c) == 'UA(2.7)'"); py_run!(py, c, "assert repr(+c) == 'UA(2.7)'");
py_run!(py, c, "assert repr(abs(c)) == 'UA(2.7)'"); py_run!(py, c, "assert repr(abs(c)) == 'UA(2.7)'");
py_run!(py, c, "assert repr(round(c)) == 'UA(3)'");
py_run!(py, c, "assert repr(round(c, 1)) == 'UA(3)'");
} }
#[pyclass] #[pyclass]

View File

@ -19,27 +19,23 @@ fn _test_compile_errors() {
t.compile_fail("tests/ui/invalid_need_module_arg_position.rs"); t.compile_fail("tests/ui/invalid_need_module_arg_position.rs");
t.compile_fail("tests/ui/invalid_property_args.rs"); t.compile_fail("tests/ui/invalid_property_args.rs");
t.compile_fail("tests/ui/invalid_pyclass_args.rs"); t.compile_fail("tests/ui/invalid_pyclass_args.rs");
t.compile_fail("tests/ui/invalid_pyclass_enum.rs");
t.compile_fail("tests/ui/invalid_pyclass_item.rs");
t.compile_fail("tests/ui/invalid_pyfunctions.rs"); t.compile_fail("tests/ui/invalid_pyfunctions.rs");
t.compile_fail("tests/ui/invalid_pymethods.rs"); t.compile_fail("tests/ui/invalid_pymethods.rs");
t.compile_fail("tests/ui/invalid_pymethod_names.rs"); t.compile_fail("tests/ui/invalid_pymethod_names.rs");
t.compile_fail("tests/ui/invalid_pymodule_args.rs");
t.compile_fail("tests/ui/invalid_argument_attributes.rs"); t.compile_fail("tests/ui/invalid_argument_attributes.rs");
t.compile_fail("tests/ui/missing_clone.rs");
t.compile_fail("tests/ui/reject_generics.rs"); t.compile_fail("tests/ui/reject_generics.rs");
t.compile_fail("tests/ui/invalid_immutable_pyclass_borrow.rs"); t.compile_fail("tests/ui/invalid_immutable_pyclass_borrow.rs");
t.compile_fail("tests/ui/wrong_aspyref_lifetimes.rs");
tests_rust_1_48(&t);
tests_rust_1_49(&t); tests_rust_1_49(&t);
tests_rust_1_54(&t); tests_rust_1_54(&t);
tests_rust_1_55(&t); tests_rust_1_55(&t);
tests_rust_1_56(&t); tests_rust_1_56(&t);
#[rustversion::since(1.48)]
fn tests_rust_1_48(t: &trybuild::TestCases) {
t.compile_fail("tests/ui/missing_clone.rs");
t.compile_fail("tests/ui/wrong_aspyref_lifetimes.rs");
}
#[rustversion::before(1.48)]
fn tests_rust_1_48(_t: &trybuild::TestCases) {}
#[rustversion::since(1.49)] #[rustversion::since(1.49)]
fn tests_rust_1_49(t: &trybuild::TestCases) { fn tests_rust_1_49(t: &trybuild::TestCases) {
t.compile_fail("tests/ui/deprecations.rs"); t.compile_fail("tests/ui/deprecations.rs");

53
tests/test_enum.rs Normal file
View File

@ -0,0 +1,53 @@
use pyo3::prelude::*;
use pyo3::{py_run, wrap_pyfunction};
mod common;
#[pyclass]
#[derive(Debug, PartialEq, Clone)]
pub enum MyEnum {
Variant,
OtherVariant,
}
#[test]
fn test_enum_class_attr() {
let gil = Python::acquire_gil();
let py = gil.python();
let my_enum = py.get_type::<MyEnum>();
py_assert!(py, my_enum, "getattr(my_enum, 'Variant', None) is not None");
py_assert!(py, my_enum, "getattr(my_enum, 'foobar', None) is None");
py_run!(py, my_enum, "my_enum.Variant = None");
}
#[pyfunction]
fn return_enum() -> MyEnum {
MyEnum::Variant
}
#[test]
#[ignore] // need to implement __eq__
fn test_return_enum() {
let gil = Python::acquire_gil();
let py = gil.python();
let f = wrap_pyfunction!(return_enum)(py).unwrap();
let mynum = py.get_type::<MyEnum>();
py_run!(py, f mynum, "assert f() == mynum.Variant")
}
#[pyfunction]
fn enum_arg(e: MyEnum) {
assert_eq!(MyEnum::OtherVariant, e)
}
#[test]
#[ignore] // need to implement __eq__
fn test_enum_arg() {
let gil = Python::acquire_gil();
let py = gil.python();
let f = wrap_pyfunction!(enum_arg)(py).unwrap();
let mynum = py.get_type::<MyEnum>();
py_run!(py, f mynum, "f(mynum.Variant)")
}

View File

@ -1,5 +1,6 @@
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::py_run; use pyo3::py_run;
use pyo3::type_object::PyTypeObject;
use pyo3::types::IntoPyDict; use pyo3::types::IntoPyDict;
@ -102,6 +103,23 @@ fn mutation_fails() {
assert_eq!(&e.to_string(), "RuntimeError: Already borrowed") assert_eq!(&e.to_string(), "RuntimeError: Already borrowed")
} }
#[test]
fn is_subclass_and_is_instance() {
let gil = Python::acquire_gil();
let py = gil.python();
let sub_ty = SubClass::type_object(py);
let base_ty = BaseClass::type_object(py);
assert!(sub_ty.is_subclass_of::<BaseClass>().unwrap());
assert!(sub_ty.is_subclass(base_ty).unwrap());
let obj = PyCell::new(py, SubClass::new()).unwrap();
assert!(obj.is_instance_of::<SubClass>().unwrap());
assert!(obj.is_instance_of::<BaseClass>().unwrap());
assert!(obj.is_instance(sub_ty).unwrap());
assert!(obj.is_instance(base_ty).unwrap());
}
#[pyclass(subclass)] #[pyclass(subclass)]
struct BaseClassWithResult { struct BaseClassWithResult {
_val: usize, _val: usize,

View File

@ -1,5 +1,3 @@
#![allow(deprecated)] // for deprecated protocol methods
use std::collections::HashMap; use std::collections::HashMap;
use pyo3::exceptions::PyKeyError; use pyo3::exceptions::PyKeyError;
@ -59,16 +57,6 @@ impl PyMappingProtocol for Mapping {
Ok(()) Ok(())
} }
} }
/// not an actual reversed implementation, just to demonstrate that the method is callable.
fn __reversed__(&self) -> PyObject {
let gil = Python::acquire_gil();
self.index
.keys()
.cloned()
.collect::<Vec<String>>()
.into_py(gil.python())
}
} }
/// Return a dict with `m = Mapping(['1', '2', '3'])`. /// Return a dict with `m = Mapping(['1', '2', '3'])`.
@ -117,12 +105,3 @@ fn test_delitem() {
py_expect_exception!(py, *d, "del m[-1]", PyTypeError); py_expect_exception!(py, *d, "del m[-1]", PyTypeError);
py_expect_exception!(py, *d, "del m['4']", PyKeyError); py_expect_exception!(py, *d, "del m['4']", PyKeyError);
} }
#[test]
fn test_reversed() {
let gil = Python::acquire_gil();
let py = gil.python();
let d = map_dict(py);
py_assert!(py, *d, "set(reversed(m)) == {'1', '2', '3'}");
}

View File

@ -23,10 +23,6 @@ impl ValueClass {
#[pyclass(module = "module")] #[pyclass(module = "module")]
struct LocatedClass {} struct LocatedClass {}
fn sum_as_string(a: i64, b: i64) -> String {
format!("{}", a + b)
}
#[pyfunction] #[pyfunction]
/// Doubles the given value /// Doubles the given value
fn double(x: usize) -> usize { fn double(x: usize) -> usize {
@ -36,12 +32,6 @@ fn double(x: usize) -> usize {
/// This module is implemented in Rust. /// This module is implemented in Rust.
#[pymodule] #[pymodule]
fn module_with_functions(_py: Python, m: &PyModule) -> PyResult<()> { fn module_with_functions(_py: Python, m: &PyModule) -> PyResult<()> {
#![allow(deprecated)]
#[pyfn(m, "sum_as_string")]
fn function_with_deprecated_name(_py: Python, a: i64, b: i64) -> String {
sum_as_string(a, b)
}
#[pyfn(m)] #[pyfn(m)]
#[pyo3(name = "no_parameters")] #[pyo3(name = "no_parameters")]
fn function_with_name() -> usize { fn function_with_name() -> usize {
@ -89,7 +79,6 @@ fn test_module_with_functions() {
*d, *d,
"module_with_functions.__doc__ == 'This module is implemented in Rust.'" "module_with_functions.__doc__ == 'This module is implemented in Rust.'"
); );
py_assert!(py, *d, "module_with_functions.sum_as_string(1, 2) == '3'");
py_assert!(py, *d, "module_with_functions.no_parameters() == 42"); py_assert!(py, *d, "module_with_functions.no_parameters() == 42");
py_assert!(py, *d, "module_with_functions.foo == 'bar'"); py_assert!(py, *d, "module_with_functions.foo == 'bar'");
py_assert!(py, *d, "module_with_functions.AnonClass != None"); py_assert!(py, *d, "module_with_functions.AnonClass != None");
@ -438,20 +427,6 @@ fn test_module_functions_with_module() {
); );
} }
#[test]
#[allow(deprecated)]
fn test_module_with_deprecated_name() {
#[pymodule(custom_name)]
fn my_module(_py: Python, _m: &PyModule) -> PyResult<()> {
Ok(())
}
Python::with_gil(|py| {
let m = pyo3::wrap_pymodule!(custom_name)(py);
py_assert!(py, m, "m.__name__ == 'custom_name'");
})
}
#[test] #[test]
fn test_module_doc_hidden() { fn test_module_doc_hidden() {
#[doc(hidden)] #[doc(hidden)]

View File

@ -2,9 +2,7 @@
//! but can't even be cfg-ed out on MSRV because the compiler doesn't support //! but can't even be cfg-ed out on MSRV because the compiler doesn't support
//! the syntax. //! the syntax.
// TODO(#1782) rustversion attribute can't go on modules until Rust 1.42, so this #[rustversion::since(1.54)]
// funky dance has to happen...
mod requires_1_54 { mod requires_1_54 {
#[rustversion::since(1.54)]
include!("not_msrv/requires_1_54.rs"); include!("not_msrv/requires_1_54.rs");
} }

View File

@ -1,11 +1,14 @@
use pyo3::exceptions::PyValueError; use pyo3::exceptions::PyValueError;
use pyo3::types::{PySlice, PyType}; use pyo3::types::{PyList, PySlice, PyType};
use pyo3::{exceptions::PyAttributeError, prelude::*}; use pyo3::{exceptions::PyAttributeError, prelude::*};
use pyo3::{ffi, py_run, AsPyPointer, PyCell}; use pyo3::{ffi, py_run, AsPyPointer, PyCell};
use std::{isize, iter}; use std::{isize, iter};
mod common; mod common;
#[pyclass]
struct EmptyClass;
#[pyclass] #[pyclass]
struct ExampleClass { struct ExampleClass {
#[pyo3(get, set)] #[pyo3(get, set)]
@ -94,7 +97,7 @@ fn test_getattr() {
assert!(example_py assert!(example_py
.getattr("other_attr") .getattr("other_attr")
.unwrap_err() .unwrap_err()
.is_instance::<PyAttributeError>(py)); .is_instance_of::<PyAttributeError>(py));
}) })
} }
@ -545,11 +548,23 @@ fn descr_getset() {
r#" r#"
class Class: class Class:
counter = Counter() counter = Counter()
# access via type
counter = Class.counter
assert counter.count == 1
# access with instance directly
assert Counter.__get__(counter, Class()).count == 2
# access via instance
c = Class() c = Class()
c.counter # count += 1
assert c.counter.count == 2
c.counter = Counter()
assert c.counter.count == 3 assert c.counter.count == 3
# __set__
c.counter = Counter()
assert c.counter.count == 4
# __delete__
del c.counter del c.counter
assert c.counter.count == 1 assert c.counter.count == 1
"# "#
@ -560,3 +575,72 @@ assert c.counter.count == 1
.map_err(|e| e.print(py)) .map_err(|e| e.print(py))
.unwrap(); .unwrap();
} }
#[pyclass]
struct NotHashable;
#[pymethods]
impl NotHashable {
#[classattr]
const __hash__: Option<PyObject> = None;
}
#[test]
fn test_hash_opt_out() {
// By default Python provides a hash implementation, which can be disabled by setting __hash__
// to None.
Python::with_gil(|py| {
let empty = Py::new(py, EmptyClass).unwrap();
py_assert!(py, empty, "hash(empty) is not None");
let not_hashable = Py::new(py, NotHashable).unwrap();
py_expect_exception!(py, not_hashable, "hash(not_hashable)", PyTypeError);
})
}
/// Class with __iter__ gets default contains from CPython.
#[pyclass]
struct DefaultedContains;
#[pymethods]
impl DefaultedContains {
fn __iter__(&self, py: Python) -> PyObject {
PyList::new(py, &["a", "b", "c"])
.as_ref()
.iter()
.unwrap()
.into()
}
}
#[pyclass]
struct NoContains;
#[pymethods]
impl NoContains {
fn __iter__(&self, py: Python) -> PyObject {
PyList::new(py, &["a", "b", "c"])
.as_ref()
.iter()
.unwrap()
.into()
}
// Equivalent to the opt-out const form in NotHashable above, just more verbose, to confirm this
// also works.
#[classattr]
fn __contains__() -> Option<PyObject> {
None
}
}
#[test]
fn test_contains_opt_out() {
Python::with_gil(|py| {
let defaulted_contains = Py::new(py, DefaultedContains).unwrap();
py_assert!(py, defaulted_contains, "'a' in defaulted_contains");
let no_contains = Py::new(py, NoContains).unwrap();
py_expect_exception!(py, no_contains, "'a' in no_contains", PyTypeError);
})
}

View File

@ -1,9 +0,0 @@
// This test checks Python initialization on python 3.6, so needs to be standalone in its own process.
#[cfg(not(PyPy))]
#[test]
fn test_py36_init_threads() {
unsafe { pyo3::ffi::Py_InitializeEx(0) };
pyo3::prepare_freethreaded_python();
assert_eq!(unsafe { pyo3::ffi::PyEval_ThreadsInitialized() }, 1);
}

View File

@ -1,8 +1,6 @@
#![allow(deprecated)] // for deprecated protocol methods
use pyo3::class::{ use pyo3::class::{
PyAsyncProtocol, PyContextProtocol, PyDescrProtocol, PyIterProtocol, PyMappingProtocol, PyAsyncProtocol, PyDescrProtocol, PyIterProtocol, PyMappingProtocol, PyObjectProtocol,
PyObjectProtocol, PySequenceProtocol, PySequenceProtocol,
}; };
use pyo3::exceptions::{PyIndexError, PyValueError}; use pyo3::exceptions::{PyIndexError, PyValueError};
use pyo3::prelude::*; use pyo3::prelude::*;
@ -91,14 +89,6 @@ impl PyObjectProtocol for StringMethods {
fn __repr__(&self) -> &'static str { fn __repr__(&self) -> &'static str {
"repr" "repr"
} }
fn __format__(&self, format_spec: String) -> String {
format!("format({})", format_spec)
}
fn __bytes__(&self) -> &'static [u8] {
b"bytes"
}
} }
#[test] #[test]
@ -109,12 +99,6 @@ fn string_methods() {
let obj = Py::new(py, StringMethods {}).unwrap(); let obj = Py::new(py, StringMethods {}).unwrap();
py_assert!(py, obj, "str(obj) == 'str'"); py_assert!(py, obj, "str(obj) == 'str'");
py_assert!(py, obj, "repr(obj) == 'repr'"); py_assert!(py, obj, "repr(obj) == 'repr'");
py_assert!(py, obj, "'{0:x}'.format(obj) == 'format(x)'");
py_assert!(py, obj, "bytes(obj) == b'bytes'");
// Test that `__bytes__` takes no arguments (should be METH_NOARGS)
py_assert!(py, obj, "obj.__bytes__() == b'bytes'");
py_expect_exception!(py, obj, "obj.__bytes__('unexpected argument')", PyTypeError);
} }
#[pyclass] #[pyclass]
@ -298,25 +282,6 @@ fn setdelitem() {
assert_eq!(c.val, None); assert_eq!(c.val, None);
} }
#[pyclass]
struct Reversed {}
#[pyproto]
impl PyMappingProtocol for Reversed {
fn __reversed__(&self) -> &'static str {
"I am reversed"
}
}
#[test]
fn reversed() {
let gil = Python::acquire_gil();
let py = gil.python();
let c = Py::new(py, Reversed {}).unwrap();
py_run!(py, c, "assert reversed(c) == 'I am reversed'");
}
#[pyclass] #[pyclass]
struct Contains {} struct Contains {}
@ -338,57 +303,6 @@ fn contains() {
py_expect_exception!(py, c, "assert 'wrong type' not in c", PyTypeError); py_expect_exception!(py, c, "assert 'wrong type' not in c", PyTypeError);
} }
#[pyclass]
struct ContextManager {
exit_called: bool,
}
#[pyproto]
impl PyContextProtocol for ContextManager {
fn __enter__(&mut self) -> i32 {
42
}
fn __exit__(
&mut self,
ty: Option<&PyType>,
_value: Option<&PyAny>,
_traceback: Option<&PyAny>,
) -> bool {
let gil = Python::acquire_gil();
self.exit_called = true;
ty == Some(gil.python().get_type::<PyValueError>())
}
}
#[test]
fn context_manager() {
let gil = Python::acquire_gil();
let py = gil.python();
let c = PyCell::new(py, ContextManager { exit_called: false }).unwrap();
py_run!(py, c, "with c as x: assert x == 42");
{
let mut c = c.borrow_mut();
assert!(c.exit_called);
c.exit_called = false;
}
py_run!(py, c, "with c as x: raise ValueError");
{
let mut c = c.borrow_mut();
assert!(c.exit_called);
c.exit_called = false;
}
py_expect_exception!(
py,
c,
"with c as x: raise NotImplementedError",
PyNotImplementedError
);
let c = c.borrow();
assert!(c.exit_called);
}
#[test] #[test]
fn test_basics() { fn test_basics() {
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();

View File

@ -6,15 +6,15 @@ error[E0277]: the trait bound `PyDict: PyClass` is not satisfied
| |
= note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict`
note: required by a bound in `PyClassBaseType` note: required by a bound in `PyClassBaseType`
--> src/class/impl_.rs:766:1 --> src/class/impl_.rs
| |
766 | / pub trait PyClassBaseType: Sized { | / pub trait PyClassBaseType: Sized {
767 | | type Dict; | | type Dict;
768 | | type WeakRef; | | type WeakRef;
769 | | type LayoutAsBase: PyCellLayout<Self>; | | type LayoutAsBase: PyCellLayout<Self>;
... | ... |
772 | | type Initializer: PyObjectInit<Self>; | | type Initializer: PyObjectInit<Self>;
773 | | } | | }
| |_^ required by this bound in `PyClassBaseType` | |_^ required by this bound in `PyClassBaseType`
= note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)
@ -26,8 +26,8 @@ error[E0277]: the trait bound `PyDict: PyClass` is not satisfied
| |
= note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict`
note: required by a bound in `ThreadCheckerInherited` note: required by a bound in `ThreadCheckerInherited`
--> src/class/impl_.rs:753:47 --> src/class/impl_.rs
| |
753 | pub struct ThreadCheckerInherited<T: Send, U: PyClassBaseType>(PhantomData<T>, U::ThreadChecker); | pub struct ThreadCheckerInherited<T: Send, U: PyClassBaseType>(PhantomData<T>, U::ThreadChecker);
| ^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited` | ^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited`
= note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -2,28 +2,6 @@
use pyo3::prelude::*; use pyo3::prelude::*;
#[pyclass]
#[text_signature = "()"]
struct TestClass {
num: u32,
}
#[pymethods]
impl TestClass {
#[classattr]
#[name = "num"]
const DEPRECATED_NAME_CONSTANT: i32 = 0;
#[name = "num"]
#[text_signature = "()"]
fn deprecated_name_pymethod(&self) { }
#[staticmethod]
#[name = "custom_static"]
#[text_signature = "()"]
fn deprecated_name_staticmethod() {}
}
#[pyclass] #[pyclass]
struct DeprecatedCall; struct DeprecatedCall;
@ -33,23 +11,6 @@ impl DeprecatedCall {
fn deprecated_call(&self) {} fn deprecated_call(&self) {}
} }
#[pyfunction]
#[name = "foo"]
#[text_signature = "()"]
fn deprecated_name_pyfunction() { }
#[pymodule(deprecated_module_name)]
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
#[pyfn(m, "some_name")]
#[text_signature = "()"]
fn deprecated_name_pyfn() { }
Ok(())
}
fn main() { fn main() {
} }
// TODO: ensure name deprecated on #[pyfunction] and #[pymodule]

View File

@ -1,77 +1,11 @@
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]` error: use of deprecated constant `pyo3::impl_::deprecations::CALL_ATTRIBUTE`: use `fn __call__` instead of `#[call]`
--> tests/ui/deprecations.rs:14:5 --> tests/ui/deprecations.rs:10:7
| |
14 | #[name = "num"] 10 | #[call]
| ^ | ^^^^
| |
note: the lint level is defined here note: the lint level is defined here
--> tests/ui/deprecations.rs:1:9 --> tests/ui/deprecations.rs:1:9
| |
1 | #![deny(deprecated)] 1 | #![deny(deprecated)]
| ^^^^^^^^^^ | ^^^^^^^^^^
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
--> tests/ui/deprecations.rs:17:5
|
17 | #[name = "num"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATTRIBUTE`: use `#[pyo3(text_signature = "...")]` instead of `#[text_signature = "..."]`
--> tests/ui/deprecations.rs:18:5
|
18 | #[text_signature = "()"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
--> tests/ui/deprecations.rs:22:5
|
22 | #[name = "custom_static"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATTRIBUTE`: use `#[pyo3(text_signature = "...")]` instead of `#[text_signature = "..."]`
--> tests/ui/deprecations.rs:23:5
|
23 | #[text_signature = "()"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::CALL_ATTRIBUTE`: use `fn __call__` instead of `#[call]`
--> tests/ui/deprecations.rs:32:7
|
32 | #[call]
| ^^^^
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
--> tests/ui/deprecations.rs:37:1
|
37 | #[name = "foo"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATTRIBUTE`: use `#[pyo3(text_signature = "...")]` instead of `#[text_signature = "..."]`
--> tests/ui/deprecations.rs:38:1
|
38 | #[text_signature = "()"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::PYFN_NAME_ARGUMENT`: use `#[pyfn(m)] #[pyo3(name = "...")]` instead of `#[pyfn(m, "...")]`
--> tests/ui/deprecations.rs:43:15
|
43 | #[pyfn(m, "some_name")]
| ^^^^^^^^^^^
error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATTRIBUTE`: use `#[pyo3(text_signature = "...")]` instead of `#[text_signature = "..."]`
--> tests/ui/deprecations.rs:44:5
|
44 | #[text_signature = "()"]
| ^
error: use of deprecated constant `pyo3::impl_::deprecations::PYMODULE_NAME_ARGUMENT`: use `#[pymodule] #[pyo3(name = "...")]` instead of `#[pymodule(...)]`
--> tests/ui/deprecations.rs:41:12
|
41 | #[pymodule(deprecated_module_name)]
| ^^^^^^^^^^^^^^^^^^^^^^
error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATTRIBUTE`: use `#[pyo3(text_signature = "...")]` instead of `#[text_signature = "..."]`
--> tests/ui/deprecations.rs:6:1
|
6 | #[text_signature = "()"]
| ^

View File

@ -0,0 +1,15 @@
use pyo3::prelude::*;
#[pyclass(subclass)]
enum NotBaseClass {
x,
y,
}
#[pyclass(extends = PyList)]
enum NotDrivedClass {
x,
y,
}
fn main() {}

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