Compare commits
14 Commits
master
...
release-0.
Author | SHA1 | Date |
---|---|---|
Paul Stemmet | 1a65112ba7 | |
David Hewitt | a646a820fd | |
David Hewitt | bd15f92a56 | |
Adam Reichold | 4f7c7065c2 | |
Adam Reichold | 3f6faf00ff | |
messense | 4e13c0eae8 | |
Icxolu | e501377587 | |
Icxolu | fd18955da8 | |
Liam | e174a04bb3 | |
dependabot[bot] | a8abe508ba | |
Jeong, Heon | 68d19738e7 | |
Joseph Perez | b8ec43e469 | |
Icxolu | 0b888cb934 | |
David Hewitt | e4cc98607e |
|
@ -8,6 +8,6 @@ Please consider adding the following to your pull request:
|
|||
- docs to all new functions and / or detail in the guide
|
||||
- tests for all new or changed functions
|
||||
|
||||
PyO3's CI pipeline will check your pull request, thus make sure you have checked the `Contributing.md` guidelines. To run most of its tests
|
||||
PyO3's CI pipeline will check your pull request. To run its tests
|
||||
locally, you can run ```nox```. See ```nox --list-sessions```
|
||||
for a list of supported actions.
|
||||
|
|
|
@ -16,7 +16,7 @@ on:
|
|||
rust-target:
|
||||
required: true
|
||||
type: string
|
||||
MSRV:
|
||||
extra-features:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
|
@ -54,18 +54,14 @@ jobs:
|
|||
name: Prepare LD_LIBRARY_PATH (Ubuntu only)
|
||||
run: echo LD_LIBRARY_PATH=${pythonLocation}/lib >> $GITHUB_ENV
|
||||
|
||||
- if: inputs.rust == inputs.MSRV
|
||||
name: Prepare MSRV package versions
|
||||
run: nox -s set-msrv-package-versions
|
||||
- if: inputs.rust == '1.56.0'
|
||||
name: Prepare minimal package versions (MSRV only)
|
||||
run: nox -s set-minimal-package-versions
|
||||
|
||||
- if: inputs.rust != 'stable'
|
||||
name: Ignore changed error messages when using trybuild
|
||||
run: echo "TRYBUILD=overwrite" >> "$GITHUB_ENV"
|
||||
|
||||
- if: inputs.rust == 'nightly'
|
||||
name: Prepare to test on nightly rust
|
||||
run: echo "MAYBE_NIGHTLY=nightly" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build docs
|
||||
run: nox -s docs
|
||||
|
||||
|
@ -92,31 +88,26 @@ jobs:
|
|||
|
||||
- name: Build (all additive features)
|
||||
if: ${{ !startsWith(inputs.python-version, 'graalpy') }}
|
||||
run: cargo build --lib --tests --no-default-features --features "multiple-pymethods full $MAYBE_NIGHTLY"
|
||||
run: cargo build --lib --tests --no-default-features --features "full ${{ inputs.extra-features }}"
|
||||
|
||||
- if: ${{ startsWith(inputs.python-version, 'pypy') }}
|
||||
name: Build PyPy (abi3-py37)
|
||||
run: cargo build --lib --tests --no-default-features --features "multiple-pymethods abi3-py37 full $MAYBE_NIGHTLY"
|
||||
run: cargo build --lib --tests --no-default-features --features "abi3-py37 full ${{ inputs.extra-features }}"
|
||||
|
||||
# Run tests (except on PyPy, because no embedding API).
|
||||
- if: ${{ !startsWith(inputs.python-version, 'pypy') && !startsWith(inputs.python-version, 'graalpy') }}
|
||||
name: Test
|
||||
run: cargo test --no-default-features --features "full $MAYBE_NIGHTLY"
|
||||
|
||||
# Repeat, with multiple-pymethods feature enabled (it's not entirely additive)
|
||||
- if: ${{ !startsWith(inputs.python-version, 'pypy') && !startsWith(inputs.python-version, 'graalpy') }}
|
||||
name: Test
|
||||
run: cargo test --no-default-features --features "multiple-pymethods full $MAYBE_NIGHTLY"
|
||||
run: cargo test --no-default-features --features "full ${{ inputs.extra-features }}"
|
||||
|
||||
# Run tests again, but in abi3 mode
|
||||
- if: ${{ !startsWith(inputs.python-version, 'pypy') && !startsWith(inputs.python-version, 'graalpy') }}
|
||||
name: Test (abi3)
|
||||
run: cargo test --no-default-features --features "multiple-pymethods abi3 full $MAYBE_NIGHTLY"
|
||||
run: cargo test --no-default-features --features "abi3 full ${{ inputs.extra-features }}"
|
||||
|
||||
# Run tests again, for abi3-py37 (the minimal Python version)
|
||||
- if: ${{ (!startsWith(inputs.python-version, 'pypy') && !startsWith(inputs.python-version, 'graalpy')) && (inputs.python-version != '3.7') }}
|
||||
name: Test (abi3-py37)
|
||||
run: cargo test --no-default-features --features "multiple-pymethods abi3-py37 full $MAYBE_NIGHTLY"
|
||||
run: cargo test --no-default-features --features "abi3-py37 full ${{ inputs.extra-features }}"
|
||||
|
||||
- name: Test proc-macro code
|
||||
run: cargo test --manifest-path=pyo3-macros-backend/Cargo.toml
|
||||
|
|
|
@ -31,18 +31,6 @@ jobs:
|
|||
- name: Check rust formatting (rustfmt)
|
||||
run: nox -s rustfmt
|
||||
|
||||
resolve:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
MSRV: ${{ steps.resolve-msrv.outputs.MSRV }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- name: resolve MSRV
|
||||
id: resolve-msrv
|
||||
run:
|
||||
echo MSRV=`python -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["package"]["rust-version"])'` >> $GITHUB_OUTPUT
|
||||
|
||||
semver-checks:
|
||||
if: github.ref != 'refs/heads/main'
|
||||
needs: [fmt]
|
||||
|
@ -53,13 +41,13 @@ jobs:
|
|||
- uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
|
||||
check-msrv:
|
||||
needs: [fmt, resolve]
|
||||
needs: [fmt]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ needs.resolve.outputs.MSRV }}
|
||||
toolchain: 1.56.0
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
components: rust-src
|
||||
- uses: actions/setup-python@v5
|
||||
|
@ -69,11 +57,9 @@ jobs:
|
|||
with:
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
- run: python -m pip install --upgrade pip && pip install nox
|
||||
# This is a smoke test to confirm that CI will run on MSRV (including dev dependencies)
|
||||
- name: Check with MSRV package versions
|
||||
run: |
|
||||
nox -s set-msrv-package-versions
|
||||
nox -s check-all
|
||||
- name: Prepare minimal package versions
|
||||
run: nox -s set-minimal-package-versions
|
||||
- run: nox -s check-all
|
||||
|
||||
env:
|
||||
CARGO_BUILD_TARGET: x86_64-unknown-linux-gnu
|
||||
|
@ -155,7 +141,7 @@ jobs:
|
|||
build-pr:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-build-full') && github.event_name == 'pull_request' }}
|
||||
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} rust-${{ matrix.rust }}
|
||||
needs: [fmt, resolve]
|
||||
needs: [fmt]
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
os: ${{ matrix.platform.os }}
|
||||
|
@ -163,12 +149,13 @@ jobs:
|
|||
python-architecture: ${{ matrix.platform.python-architecture }}
|
||||
rust: ${{ matrix.rust }}
|
||||
rust-target: ${{ matrix.platform.rust-target }}
|
||||
MSRV: ${{ needs.resolve.outputs.MSRV }}
|
||||
extra-features: ${{ matrix.platform.extra-features }}
|
||||
secrets: inherit
|
||||
strategy:
|
||||
# If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present
|
||||
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
|
||||
matrix:
|
||||
extra-features: ["multiple-pymethods"]
|
||||
rust: [stable]
|
||||
python-version: ["3.12"]
|
||||
platform:
|
||||
|
@ -210,10 +197,11 @@ jobs:
|
|||
python-architecture: "x64",
|
||||
rust-target: "x86_64-unknown-linux-gnu",
|
||||
}
|
||||
extra-features: "nightly multiple-pymethods"
|
||||
build-full:
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
|
||||
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} rust-${{ matrix.rust }}
|
||||
needs: [fmt, resolve]
|
||||
needs: [fmt]
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
os: ${{ matrix.platform.os }}
|
||||
|
@ -221,12 +209,13 @@ jobs:
|
|||
python-architecture: ${{ matrix.platform.python-architecture }}
|
||||
rust: ${{ matrix.rust }}
|
||||
rust-target: ${{ matrix.platform.rust-target }}
|
||||
MSRV: ${{ needs.resolve.outputs.MSRV }}
|
||||
extra-features: ${{ matrix.platform.extra-features }}
|
||||
secrets: inherit
|
||||
strategy:
|
||||
# If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present
|
||||
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
|
||||
matrix:
|
||||
extra-features: ["multiple-pymethods"] # Because MSRV doesn't support this
|
||||
rust: [stable]
|
||||
python-version: [
|
||||
"3.7",
|
||||
|
@ -235,7 +224,6 @@ jobs:
|
|||
"3.10",
|
||||
"3.11",
|
||||
"3.12",
|
||||
"3.13-dev",
|
||||
"pypy3.7",
|
||||
"pypy3.8",
|
||||
"pypy3.9",
|
||||
|
@ -267,7 +255,7 @@ jobs:
|
|||
]
|
||||
include:
|
||||
# Test minimal supported Rust version
|
||||
- rust: ${{ needs.resolve.outputs.MSRV }}
|
||||
- rust: 1.56.0
|
||||
python-version: "3.12"
|
||||
platform:
|
||||
{
|
||||
|
@ -275,6 +263,7 @@ jobs:
|
|||
python-architecture: "x64",
|
||||
rust-target: "x86_64-unknown-linux-gnu",
|
||||
}
|
||||
extra-features: ""
|
||||
|
||||
# Test the `nightly` feature
|
||||
- rust: nightly
|
||||
|
@ -285,6 +274,7 @@ jobs:
|
|||
python-architecture: "x64",
|
||||
rust-target: "x86_64-unknown-linux-gnu",
|
||||
}
|
||||
extra-features: "nightly multiple-pymethods"
|
||||
|
||||
# Run rust beta to help catch toolchain regressions
|
||||
- rust: beta
|
||||
|
@ -295,6 +285,7 @@ jobs:
|
|||
python-architecture: "x64",
|
||||
rust-target: "x86_64-unknown-linux-gnu",
|
||||
}
|
||||
extra-features: "multiple-pymethods"
|
||||
|
||||
# Test 32-bit Windows only with the latest Python version
|
||||
- rust: stable
|
||||
|
@ -305,6 +296,7 @@ jobs:
|
|||
python-architecture: "x86",
|
||||
rust-target: "i686-pc-windows-msvc",
|
||||
}
|
||||
extra-features: "multiple-pymethods"
|
||||
|
||||
# test arm macos runner with the latest Python version
|
||||
# NB: if the full matrix switchess to arm, switch to x86_64 here
|
||||
|
@ -316,6 +308,7 @@ jobs:
|
|||
python-architecture: "arm64",
|
||||
rust-target: "aarch64-apple-darwin",
|
||||
}
|
||||
extra-features: "multiple-pymethods"
|
||||
|
||||
valgrind:
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
|
||||
|
@ -503,31 +496,21 @@ jobs:
|
|||
- run: python3 -m nox -s test-version-limits
|
||||
|
||||
check-feature-powerset:
|
||||
needs: [fmt, resolve]
|
||||
needs: [fmt]
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
|
||||
runs-on: ubuntu-latest
|
||||
name: check-feature-powerset ${{ matrix.rust }}
|
||||
strategy:
|
||||
# run on stable and MSRV to check that all combinations of features are expected to build fine on our supported
|
||||
# range of compilers
|
||||
matrix:
|
||||
rust: ["stable"]
|
||||
include:
|
||||
- rust: ${{ needs.resolve.outputs.MSRV }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
- uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-hack,cargo-minimal-versions
|
||||
components: rust-src
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
- run: python3 -m pip install --upgrade pip && pip install nox
|
||||
- run: python3 -m nox -s check-feature-powerset -- ${{ matrix.rust != 'stable' && 'minimal-versions' || '' }}
|
||||
- run: python3 -m nox -s check-feature-powerset
|
||||
|
||||
test-cross-compilation:
|
||||
needs: [fmt]
|
||||
|
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
tag_name: ${{ steps.prepare_tag.outputs.tag_name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
|
||||
- name: Setup mdBook
|
||||
|
@ -52,3 +52,88 @@ jobs:
|
|||
publish_dir: ./target/guide/
|
||||
destination_dir: ${{ steps.prepare_tag.outputs.tag_name }}
|
||||
full_commit_message: "Upload documentation for ${{ steps.prepare_tag.outputs.tag_name }}"
|
||||
|
||||
cargo-benchmark:
|
||||
if: ${{ github.ref_name == 'main' }}
|
||||
name: Cargo benchmark
|
||||
needs: guide-build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: cargo-${{ runner.os }}-bench-${{ hashFiles('**/Cargo.toml') }}
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
python -m pip install --upgrade pip && pip install nox
|
||||
for bench in pyo3-benches/benches/*.rs; do
|
||||
bench_name=$(basename "$bench" .rs)
|
||||
nox -s bench -- --bench "$bench_name" -- --output-format bencher | tee -a output.txt
|
||||
done
|
||||
|
||||
# Download previous benchmark result from cache (if exists)
|
||||
- name: Download previous benchmark data
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ./cache
|
||||
key: ${{ runner.os }}-benchmark
|
||||
|
||||
# Run `github-action-benchmark` action
|
||||
- name: Store benchmark result
|
||||
uses: benchmark-action/github-action-benchmark@v1
|
||||
with:
|
||||
name: pyo3-bench
|
||||
# What benchmark tool the output.txt came from
|
||||
tool: "cargo"
|
||||
# Where the output from the benchmark tool is stored
|
||||
output-file-path: output.txt
|
||||
# GitHub API token to make a commit comment
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto-push: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
pytest-benchmark:
|
||||
if: ${{ github.ref_name == 'main' }}
|
||||
name: Pytest benchmark
|
||||
needs: cargo-benchmark
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: cargo-${{ runner.os }}-pytest-bench-${{ hashFiles('**/Cargo.toml') }}
|
||||
continue-on-error: true
|
||||
|
||||
- name: Download previous benchmark data
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ./cache
|
||||
key: ${{ runner.os }}-pytest-benchmark
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
python -m pip install --upgrade pip && pip install nox
|
||||
nox -f pytests/noxfile.py -s bench -- --benchmark-json $(pwd)/output.json
|
||||
- name: Store benchmark result
|
||||
uses: benchmark-action/github-action-benchmark@v1
|
||||
with:
|
||||
name: pytest-bench
|
||||
tool: "pytest"
|
||||
output-file-path: output.json
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto-push: ${{ github.event_name != 'pull_request' }}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
set -uex
|
||||
|
||||
rustup update nightly
|
||||
rustup default nightly
|
||||
|
||||
PYO3_VERSION=$(cargo search pyo3 --limit 1 | head -1 | tr -s ' ' | cut -d ' ' -f 3 | tr -d '"')
|
||||
|
|
83
CHANGELOG.md
83
CHANGELOG.md
|
@ -10,85 +10,6 @@ To see unreleased changes, please see the [CHANGELOG on the main branch guide](h
|
|||
|
||||
<!-- towncrier release notes start -->
|
||||
|
||||
## [0.22.1] - 2024-07-06
|
||||
|
||||
### Added
|
||||
|
||||
- Add `#[pyo3(submodule)]` option for declarative `#[pymodule]`s. [#4301](https://github.com/PyO3/pyo3/pull/4301)
|
||||
- Implement `PartialEq<bool>` for `Bound<'py, PyBool>`. [#4305](https://github.com/PyO3/pyo3/pull/4305)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Return `NotImplemented` instead of raising `TypeError` from generated equality method when comparing different types. [#4287](https://github.com/PyO3/pyo3/pull/4287)
|
||||
- Handle full-path `#[pyo3::prelude::pymodule]` and similar for `#[pyclass]` and `#[pyfunction]` in declarative modules.[#4288](https://github.com/PyO3/pyo3/pull/4288)
|
||||
- Fix 128-bit int regression on big-endian platforms with Python <3.13. [#4291](https://github.com/PyO3/pyo3/pull/4291)
|
||||
- Stop generating code that will never be covered with declarative modules. [#4297](https://github.com/PyO3/pyo3/pull/4297)
|
||||
- Fix invalid deprecation warning for trailing optional on `#[setter]` function. [#4304](https://github.com/PyO3/pyo3/pull/4304)
|
||||
|
||||
|
||||
## [0.22.0] - 2024-06-24
|
||||
|
||||
### Packaging
|
||||
|
||||
- Update `heck` dependency to 0.5. [#3966](https://github.com/PyO3/pyo3/pull/3966)
|
||||
- Extend range of supported versions of `chrono-tz` optional dependency to include version 0.10. [#4061](https://github.com/PyO3/pyo3/pull/4061)
|
||||
- Update MSRV to 1.63. [#4129](https://github.com/PyO3/pyo3/pull/4129)
|
||||
- Add optional `num-rational` feature to add conversions with Python's `fractions.Fraction`. [#4148](https://github.com/PyO3/pyo3/pull/4148)
|
||||
- Support Python 3.13. [#4184](https://github.com/PyO3/pyo3/pull/4184)
|
||||
|
||||
### Added
|
||||
|
||||
- Add `PyWeakref`, `PyWeakrefReference` and `PyWeakrefProxy`. [#3835](https://github.com/PyO3/pyo3/pull/3835)
|
||||
- Support `#[pyclass]` on enums that have tuple variants. [#4072](https://github.com/PyO3/pyo3/pull/4072)
|
||||
- Add support for scientific notation in `Decimal` conversion. [#4079](https://github.com/PyO3/pyo3/pull/4079)
|
||||
- Add `pyo3_disable_reference_pool` conditional compilation flag to avoid the overhead of the global reference pool at the cost of known limitations as explained in the performance section of the guide. [#4095](https://github.com/PyO3/pyo3/pull/4095)
|
||||
- Add `#[pyo3(constructor = (...))]` to customize the generated constructors for complex enum variants. [#4158](https://github.com/PyO3/pyo3/pull/4158)
|
||||
- Add `PyType::module`, which always matches Python `__module__`. [#4196](https://github.com/PyO3/pyo3/pull/4196)
|
||||
- Add `PyType::fully_qualified_name` which matches the "fully qualified name" defined in [PEP 737](https://peps.python.org/pep-0737). [#4196](https://github.com/PyO3/pyo3/pull/4196)
|
||||
- Add `PyTypeMethods::mro` and `PyTypeMethods::bases`. [#4197](https://github.com/PyO3/pyo3/pull/4197)
|
||||
- Add `#[pyclass(ord)]` to implement ordering based on `PartialOrd`. [#4202](https://github.com/PyO3/pyo3/pull/4202)
|
||||
- Implement `ToPyObject` and `IntoPy<PyObject>` for `PyBackedStr` and `PyBackedBytes`. [#4205](https://github.com/PyO3/pyo3/pull/4205)
|
||||
- Add `#[pyclass(hash)]` option to implement `__hash__` in terms of the `Hash` implementation [#4206](https://github.com/PyO3/pyo3/pull/4206)
|
||||
- Add `#[pyclass(eq)]` option to generate `__eq__` based on `PartialEq`, and `#[pyclass(eq_int)]` for simple enums to implement equality based on their discriminants. [#4210](https://github.com/PyO3/pyo3/pull/4210)
|
||||
- Implement `From<Bound<'py, T>>` for `PyClassInitializer<T>`. [#4214](https://github.com/PyO3/pyo3/pull/4214)
|
||||
- Add `as_super` methods to `PyRef` and `PyRefMut` for accesing the base class by reference. [#4219](https://github.com/PyO3/pyo3/pull/4219)
|
||||
- Implement `PartialEq<str>` for `Bound<'py, PyString>`. [#4245](https://github.com/PyO3/pyo3/pull/4245)
|
||||
- Implement `PyModuleMethods::filename` on PyPy. [#4249](https://github.com/PyO3/pyo3/pull/4249)
|
||||
- Implement `PartialEq<[u8]>` for `Bound<'py, PyBytes>`. [#4250](https://github.com/PyO3/pyo3/pull/4250)
|
||||
- Add `pyo3_ffi::c_str` macro to create `&'static CStr` on Rust versions which don't have 1.77's `c""` literals. [#4255](https://github.com/PyO3/pyo3/pull/4255)
|
||||
- Support `bool` conversion with `numpy` 2.0's `numpy.bool` type [#4258](https://github.com/PyO3/pyo3/pull/4258)
|
||||
- Add `PyAnyMethods::{bitnot, matmul, floor_div, rem, divmod}`. [#4264](https://github.com/PyO3/pyo3/pull/4264)
|
||||
|
||||
### Changed
|
||||
|
||||
- Change the type of `PySliceIndices::slicelength` and the `length` parameter of `PySlice::indices()`. [#3761](https://github.com/PyO3/pyo3/pull/3761)
|
||||
- Deprecate implicit default for trailing optional arguments [#4078](https://github.com/PyO3/pyo3/pull/4078)
|
||||
- `Clone`ing pointers into the Python heap has been moved behind the `py-clone` feature, as it must panic without the GIL being held as a soundness fix. [#4095](https://github.com/PyO3/pyo3/pull/4095)
|
||||
- Add `#[track_caller]` to all `Py<T>`, `Bound<'py, T>` and `Borrowed<'a, 'py, T>` methods which can panic. [#4098](https://github.com/PyO3/pyo3/pull/4098)
|
||||
- Change `PyAnyMethods::dir` to be fallible and return `PyResult<Bound<'py, PyList>>` (and similar for `PyAny::dir`). [#4100](https://github.com/PyO3/pyo3/pull/4100)
|
||||
- The global reference pool (to track pending reference count decrements) is now initialized lazily to avoid the overhead of taking a mutex upon function entry when the functionality is not actually used. [#4178](https://github.com/PyO3/pyo3/pull/4178)
|
||||
- Emit error messages when using `weakref` or `dict` when compiling for `abi3` for Python older than 3.9. [#4194](https://github.com/PyO3/pyo3/pull/4194)
|
||||
- Change `PyType::name` to always match Python `__name__`. [#4196](https://github.com/PyO3/pyo3/pull/4196)
|
||||
- Remove CPython internal ffi call for complex number including: add, sub, mul, div, neg, abs, pow. Added PyAnyMethods::{abs, pos, neg} [#4201](https://github.com/PyO3/pyo3/pull/4201)
|
||||
- Deprecate implicit integer comparision for simple enums in favor of `#[pyclass(eq_int)]`. [#4210](https://github.com/PyO3/pyo3/pull/4210)
|
||||
- Set the `module=` attribute of declarative modules' child `#[pymodule]`s and `#[pyclass]`es. [#4213](https://github.com/PyO3/pyo3/pull/4213)
|
||||
- Set the `module` option for complex enum variants from the value set on the complex enum `module`. [#4228](https://github.com/PyO3/pyo3/pull/4228)
|
||||
- Respect the Python "limited API" when building for the `abi3` feature on PyPy or GraalPy. [#4237](https://github.com/PyO3/pyo3/pull/4237)
|
||||
- Optimize code generated by `#[pyo3(get)]` on `#[pyclass]` fields. [#4254](https://github.com/PyO3/pyo3/pull/4254)
|
||||
- `PyCFunction::new`, `PyCFunction::new_with_keywords` and `PyCFunction::new_closure` now take `&'static CStr` name and doc arguments (previously was `&'static str`). [#4255](https://github.com/PyO3/pyo3/pull/4255)
|
||||
- The `experimental-declarative-modules` feature is now stabilized and available by default. [#4257](https://github.com/PyO3/pyo3/pull/4257)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix panic when `PYO3_CROSS_LIB_DIR` is set to a missing path. [#4043](https://github.com/PyO3/pyo3/pull/4043)
|
||||
- Fix a compile error when exporting an exception created with `create_exception!` living in a different Rust module using the `declarative-module` feature. [#4086](https://github.com/PyO3/pyo3/pull/4086)
|
||||
- Fix FFI definitions of `PY_VECTORCALL_ARGUMENTS_OFFSET` and `PyVectorcall_NARGS` to fix a false-positive assertion. [#4104](https://github.com/PyO3/pyo3/pull/4104)
|
||||
- Disable `PyUnicode_DATA` on PyPy: not exposed by PyPy. [#4116](https://github.com/PyO3/pyo3/pull/4116)
|
||||
- Correctly handle `#[pyo3(from_py_with = ...)]` attribute on dunder (`__magic__`) method arguments instead of silently ignoring it. [#4117](https://github.com/PyO3/pyo3/pull/4117)
|
||||
- Fix a compile error when declaring a standalone function or class method with a Python name that is a Rust keyword. [#4226](https://github.com/PyO3/pyo3/pull/4226)
|
||||
- Fix declarative modules discarding doc comments on the `mod` node. [#4236](https://github.com/PyO3/pyo3/pull/4236)
|
||||
- Fix `__dict__` attribute missing for `#[pyclass(dict)]` instances when building for `abi3` on Python 3.9. [#4251](https://github.com/PyO3/pyo3/pull/4251)
|
||||
|
||||
## [0.21.2] - 2024-04-16
|
||||
|
||||
### Changed
|
||||
|
@ -1824,9 +1745,7 @@ Yanked
|
|||
|
||||
- Initial release
|
||||
|
||||
[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.22.1...HEAD
|
||||
[0.22.1]: https://github.com/pyo3/pyo3/compare/v0.22.0...v0.22.1
|
||||
[0.22.0]: https://github.com/pyo3/pyo3/compare/v0.21.2...v0.22.0
|
||||
[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.21.2...HEAD
|
||||
[0.21.2]: https://github.com/pyo3/pyo3/compare/v0.21.1...v0.21.2
|
||||
[0.21.1]: https://github.com/pyo3/pyo3/compare/v0.21.0...v0.21.1
|
||||
[0.21.0]: https://github.com/pyo3/pyo3/compare/v0.20.3...v0.21.0
|
||||
|
|
45
Cargo.toml
45
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3"
|
||||
version = "0.23.0-dev"
|
||||
version = "0.21.2"
|
||||
description = "Bindings to Python interpreter"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
readme = "README.md"
|
||||
|
@ -12,19 +12,20 @@ categories = ["api-bindings", "development-tools::ffi"]
|
|||
license = "MIT OR Apache-2.0"
|
||||
exclude = ["/.gitignore", ".cargo/config", "/codecov.yml", "/Makefile", "/pyproject.toml", "/noxfile.py", "/.github", "/tests/test_compile_error.rs", "/tests/ui"]
|
||||
edition = "2021"
|
||||
rust-version = "1.63"
|
||||
rust-version = "1.56"
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1.0"
|
||||
libc = "0.2.62"
|
||||
parking_lot = ">= 0.11, < 0.13"
|
||||
memoffset = "0.9"
|
||||
once_cell = "1.13"
|
||||
portable-atomic = "1.0"
|
||||
|
||||
# ffi bindings to the python interpreter, split into a separate crate so they can be used independently
|
||||
pyo3-ffi = { path = "pyo3-ffi", version = "=0.23.0-dev" }
|
||||
pyo3-ffi = { path = "pyo3-ffi", version = "=0.21.2" }
|
||||
|
||||
# support crates for macros feature
|
||||
pyo3-macros = { path = "pyo3-macros", version = "=0.23.0-dev", optional = true }
|
||||
pyo3-macros = { path = "pyo3-macros", version = "=0.21.2", optional = true }
|
||||
indoc = { version = "2.0.1", optional = true }
|
||||
unindent = { version = "0.2.1", optional = true }
|
||||
|
||||
|
@ -32,27 +33,23 @@ unindent = { version = "0.2.1", optional = true }
|
|||
inventory = { version = "0.3.0", optional = true }
|
||||
|
||||
# crate integrations that can be added using the eponymous features
|
||||
anyhow = { version = "1.0.1", optional = true }
|
||||
anyhow = { version = "1.0", optional = true }
|
||||
chrono = { version = "0.4.25", default-features = false, optional = true }
|
||||
chrono-tz = { version = ">= 0.6, < 0.10", default-features = false, optional = true }
|
||||
chrono-tz = { version = ">= 0.6, < 0.9", default-features = false, optional = true }
|
||||
either = { version = "1.9", optional = true }
|
||||
eyre = { version = ">= 0.4, < 0.7", optional = true }
|
||||
hashbrown = { version = ">= 0.9, < 0.15", optional = true }
|
||||
indexmap = { version = ">= 1.6, < 3", optional = true }
|
||||
num-bigint = { version = "0.4.2", optional = true }
|
||||
num-bigint = { version = "0.4", optional = true }
|
||||
num-complex = { version = ">= 0.2, < 0.5", optional = true }
|
||||
num-rational = {version = "0.4.1", optional = true }
|
||||
rust_decimal = { version = "1.15", default-features = false, optional = true }
|
||||
rust_decimal = { version = "1.0.0", default-features = false, optional = true }
|
||||
serde = { version = "1.0", optional = true }
|
||||
smallvec = { version = "1.0", optional = true }
|
||||
|
||||
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
|
||||
portable-atomic = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_approx_eq = "1.1.0"
|
||||
chrono = "0.4.25"
|
||||
chrono-tz = ">= 0.6, < 0.10"
|
||||
chrono-tz = ">= 0.6, < 0.9"
|
||||
# Required for "and $N others" normalization
|
||||
trybuild = ">=1.0.70"
|
||||
proptest = { version = "1.0", default-features = false, features = ["std"] }
|
||||
|
@ -63,7 +60,7 @@ rayon = "1.6.1"
|
|||
futures = "0.3.28"
|
||||
|
||||
[build-dependencies]
|
||||
pyo3-build-config = { path = "pyo3-build-config", version = "=0.23.0-dev", features = ["resolve-config"] }
|
||||
pyo3-build-config = { path = "pyo3-build-config", version = "=0.21.2", features = ["resolve-config"] }
|
||||
|
||||
[features]
|
||||
default = ["macros"]
|
||||
|
@ -75,6 +72,9 @@ experimental-async = ["macros", "pyo3-macros/experimental-async"]
|
|||
# and IntoPy traits
|
||||
experimental-inspect = []
|
||||
|
||||
# Enables annotating Rust inline modules with #[pymodule] to build Python modules declaratively
|
||||
experimental-declarative-modules = ["pyo3-macros/experimental-declarative-modules", "macros"]
|
||||
|
||||
# Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc.
|
||||
macros = ["pyo3-macros", "indoc", "unindent"]
|
||||
|
||||
|
@ -104,32 +104,31 @@ generate-import-lib = ["pyo3-ffi/generate-import-lib"]
|
|||
auto-initialize = []
|
||||
|
||||
# Allows use of the deprecated "GIL Refs" APIs.
|
||||
gil-refs = ["pyo3-macros/gil-refs"]
|
||||
|
||||
# Enables `Clone`ing references to Python objects `Py<T>` which panics if the GIL is not held.
|
||||
py-clone = []
|
||||
gil-refs = []
|
||||
|
||||
# Optimizes PyObject to Vec conversion and so on.
|
||||
nightly = []
|
||||
|
||||
# Disables the checks for use in subinterpreters.
|
||||
unsafe-allow-subinterpreters = []
|
||||
|
||||
# Activates all additional features
|
||||
# This is mostly intended for testing purposes - activating *all* of these isn't particularly useful.
|
||||
full = [
|
||||
"macros",
|
||||
# "multiple-pymethods", # Not supported by wasm
|
||||
# "multiple-pymethods", # TODO re-add this when MSRV is greater than 1.62
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"either",
|
||||
"experimental-async",
|
||||
"experimental-declarative-modules",
|
||||
"experimental-inspect",
|
||||
"eyre",
|
||||
"hashbrown",
|
||||
"indexmap",
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"num-rational",
|
||||
"py-clone",
|
||||
"rust_decimal",
|
||||
"serde",
|
||||
"smallvec",
|
||||
|
@ -168,7 +167,7 @@ used_underscore_binding = "warn"
|
|||
[workspace.lints.rust]
|
||||
elided_lifetimes_in_paths = "warn"
|
||||
invalid_doc_attributes = "warn"
|
||||
rust_2018_idioms = { level = "warn", priority = -1 }
|
||||
rust_2018_idioms = "warn"
|
||||
rust_2021_prelude_collisions = "warn"
|
||||
unused_lifetimes = "warn"
|
||||
|
||||
|
|
|
@ -23,6 +23,10 @@ To work and develop PyO3, you need Python & Rust installed on your system.
|
|||
* [virtualenv](https://virtualenv.pypa.io/en/latest/) can also be used with or without Pyenv to use specific installed Python versions.
|
||||
* [`nox`][nox] is used to automate many of our CI tasks.
|
||||
|
||||
### Caveats
|
||||
|
||||
* When using pyenv on macOS, installing a Python version using `--enable-shared` is required to make it work. i.e `env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.7.12`
|
||||
|
||||
### Help users identify bugs
|
||||
|
||||
The [PyO3 Discord server](https://discord.gg/33kcChzH7f) is very active with users who are new to PyO3, and often completely new to Rust. Helping them debug is a great way to get experience with the PyO3 codebase.
|
||||
|
@ -88,7 +92,9 @@ Here are a few things to note when you are writing PRs.
|
|||
|
||||
### Continuous Integration
|
||||
|
||||
The PyO3 repo uses GitHub Actions. PRs are blocked from merging if CI is not successful. Formatting, linting and tests are checked for all Rust and Python code. In addition, all warnings in Rust code are disallowed (using `RUSTFLAGS="-D warnings"`).
|
||||
The PyO3 repo uses GitHub Actions. PRs are blocked from merging if CI is not successful.
|
||||
|
||||
Formatting, linting and tests are checked for all Rust and Python code. In addition, all warnings in Rust code are disallowed (using `RUSTFLAGS="-D warnings"`).
|
||||
|
||||
Tests run with all supported Python versions with the latest stable Rust compiler, as well as for Python 3.9 with the minimum supported Rust version.
|
||||
|
||||
|
@ -97,27 +103,9 @@ If you are adding a new feature, you should add it to the `full` feature in our
|
|||
You can run these tests yourself with
|
||||
`nox`. Use `nox -l` to list the full set of subcommands you can run.
|
||||
|
||||
#### Linting Python code
|
||||
`nox -s ruff`
|
||||
|
||||
#### Linting Rust code
|
||||
`nox -s rustfmt`
|
||||
|
||||
#### Semver checks
|
||||
`cargo semver-checks check-release`
|
||||
|
||||
#### Clippy
|
||||
`nox -s clippy-all`
|
||||
|
||||
#### Tests
|
||||
`cargo test --features full`
|
||||
|
||||
#### Check all conditional compilation
|
||||
`nox -s check-feature-powerset`
|
||||
|
||||
#### UI Tests
|
||||
|
||||
PyO3 uses [`trybuild`](https://github.com/dtolnay/trybuild) to develop UI tests to capture error messages from the Rust compiler for some of the macro functionality.
|
||||
PyO3 uses [`trybuild`][trybuild] to develop UI tests to capture error messages from the Rust compiler for some of the macro functionality.
|
||||
|
||||
Because there are several feature combinations for these UI tests, when updating them all (e.g. for a new Rust compiler version) it may be helpful to use the `update-ui-tests` nox session:
|
||||
|
||||
|
|
13
README.md
13
README.md
|
@ -1,10 +1,10 @@
|
|||
# PyO3
|
||||
|
||||
[![actions status](https://img.shields.io/github/actions/workflow/status/PyO3/pyo3/ci.yml?branch=main&logo=github&style=)](https://github.com/PyO3/pyo3/actions)
|
||||
[![benchmark](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/PyO3/pyo3)
|
||||
[![benchmark](https://img.shields.io/badge/benchmark-✓-Green?logo=github)](https://pyo3.rs/dev/bench/)
|
||||
[![codecov](https://img.shields.io/codecov/c/gh/PyO3/pyo3?logo=codecov)](https://codecov.io/gh/PyO3/pyo3)
|
||||
[![crates.io](https://img.shields.io/crates/v/pyo3?logo=rust)](https://crates.io/crates/pyo3)
|
||||
[![minimum rustc 1.63](https://img.shields.io/badge/rustc-1.63+-blue?logo=rust)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
|
||||
[![minimum rustc 1.56](https://img.shields.io/badge/rustc-1.56+-blue?logo=rust)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
|
||||
[![discord server](https://img.shields.io/discord/1209263839632424990?logo=discord)](https://discord.gg/33kcChzH7f)
|
||||
[![contributing notes](https://img.shields.io/badge/contribute-on%20github-Green?logo=github)](https://github.com/PyO3/pyo3/blob/main/Contributing.md)
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
|||
|
||||
PyO3 supports the following software versions:
|
||||
- Python 3.7 and up (CPython, PyPy, and GraalPy)
|
||||
- Rust 1.63 and up
|
||||
- Rust 1.56 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.
|
||||
|
||||
|
@ -68,7 +68,7 @@ name = "string_sum"
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
pyo3 = { version = "0.22.1", features = ["extension-module"] }
|
||||
pyo3 = { version = "0.21.2", features = ["extension-module"] }
|
||||
```
|
||||
|
||||
**`src/lib.rs`**
|
||||
|
@ -137,7 +137,7 @@ Start a new project with `cargo new` and add `pyo3` to the `Cargo.toml` like th
|
|||
|
||||
```toml
|
||||
[dependencies.pyo3]
|
||||
version = "0.22.1"
|
||||
version = "0.21.2"
|
||||
features = ["auto-initialize"]
|
||||
```
|
||||
|
||||
|
@ -176,7 +176,6 @@ about this topic.
|
|||
- [pythonize](https://github.com/davidhewitt/pythonize) _Serde serializer for converting Rust objects to JSON-compatible Python objects_
|
||||
- [pyo3-asyncio](https://github.com/awestlake87/pyo3-asyncio) _Utilities for working with Python's Asyncio library and async functions_
|
||||
- [rustimport](https://github.com/mityax/rustimport) _Directly import Rust files or crates from Python, without manual compilation step. Provides pyo3 integration by default and generates pyo3 binding code automatically._
|
||||
- [pyo3-arrow](https://crates.io/crates/pyo3-arrow) _Lightweight [Apache Arrow](https://arrow.apache.org/) integration for pyo3._
|
||||
|
||||
## Examples
|
||||
|
||||
|
@ -193,7 +192,6 @@ about this topic.
|
|||
- [fastuuid](https://github.com/thedrow/fastuuid/) _Python bindings to Rust's UUID library._
|
||||
- [feos](https://github.com/feos-org/feos) _Lightning fast thermodynamic modeling in Rust with fully developed Python interface._
|
||||
- [forust](https://github.com/jinlow/forust) _A lightweight gradient boosted decision tree library written in Rust._
|
||||
- [granian](https://github.com/emmett-framework/granian) _A Rust HTTP server for Python applications._
|
||||
- [greptimedb](https://github.com/GreptimeTeam/greptimedb/tree/main/src/script) _Support [Python scripting](https://docs.greptime.com/user-guide/python-scripts/overview) in the database_
|
||||
- [haem](https://github.com/BooleanCat/haem) _A Python library for working on Bioinformatics problems._
|
||||
- [html-py-ever](https://github.com/PyO3/setuptools-rust/tree/main/examples/html-py-ever) _Using [html5ever](https://github.com/servo/html5ever) through [kuchiki](https://github.com/kuchiki-rs/kuchiki) to speed up html parsing and css-selecting._
|
||||
|
@ -212,7 +210,6 @@ about this topic.
|
|||
- [pyheck](https://github.com/kevinheavey/pyheck) _Fast case conversion library, built by wrapping [heck](https://github.com/withoutboats/heck)._
|
||||
- Quite easy to follow as there's not much code.
|
||||
- [pyre](https://github.com/Project-Dream-Weaver/pyre-http) _Fast Python HTTP server written in Rust._
|
||||
- [pyreqwest_impersonate](https://github.com/deedy5/pyreqwest_impersonate) _The fastest python HTTP client that can impersonate web browsers by mimicking their headers and TLS/JA3/JA4/HTTP2 fingerprints._
|
||||
- [ril-py](https://github.com/Cryptex-github/ril-py) _A performant and high-level image processing library for Python written in Rust._
|
||||
- [river](https://github.com/online-ml/river) _Online machine learning in python, the computationally heavy statistics algorithms are implemented in Rust._
|
||||
- [rust-python-coverage](https://github.com/cjermain/rust-python-coverage) _Example PyO3 project with automated test coverage for Rust and Python._
|
||||
|
|
3
build.rs
3
build.rs
|
@ -39,14 +39,13 @@ fn configure_pyo3() -> Result<()> {
|
|||
println!("{}", cfg)
|
||||
}
|
||||
|
||||
// Emit cfgs like `invalid_from_utf8_lint`
|
||||
// Emit cfgs like `thread_local_const_init`
|
||||
print_feature_cfgs();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pyo3_build_config::print_expected_cfgs();
|
||||
if let Err(e) = configure_pyo3() {
|
||||
eprintln!("error: {}", e.report());
|
||||
std::process::exit(1)
|
||||
|
|
|
@ -10,5 +10,5 @@ pyo3 = { path = "..", features = ["auto-initialize", "extension-module"] }
|
|||
[[example]]
|
||||
name = "decorator"
|
||||
path = "decorator/src/lib.rs"
|
||||
crate-type = ["cdylib"]
|
||||
crate_type = ["cdylib"]
|
||||
doc-scrape-examples = true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
variable::set("PYO3_VERSION", "0.23.0-dev");
|
||||
variable::set("PYO3_VERSION", "0.21.2");
|
||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||
file::rename(".template/pyproject.toml", "pyproject.toml");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
use pyo3::exceptions::PyTypeError;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PySlice;
|
||||
use std::os::raw::c_long;
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
enum IntOrSlice<'py> {
|
||||
|
@ -28,7 +29,7 @@ impl ExampleContainer {
|
|||
} else if let Ok(slice) = key.downcast::<PySlice>() {
|
||||
// METHOD 1 - the use PySliceIndices to help with bounds checking and for cases when only start or end are provided
|
||||
// in this case the start/stop/step all filled in to give valid values based on the max_length given
|
||||
let index = slice.indices(self.max_length as isize).unwrap();
|
||||
let index = slice.indices(self.max_length as c_long).unwrap();
|
||||
let _delta = index.stop - index.start;
|
||||
|
||||
// METHOD 2 - Do the getattr manually really only needed if you have some special cases for stop/_step not being present
|
||||
|
@ -61,7 +62,7 @@ impl ExampleContainer {
|
|||
fn __setitem__(&self, idx: IntOrSlice, value: u32) -> PyResult<()> {
|
||||
match idx {
|
||||
IntOrSlice::Slice(slice) => {
|
||||
let index = slice.indices(self.max_length as isize).unwrap();
|
||||
let index = slice.indices(self.max_length as c_long).unwrap();
|
||||
println!(
|
||||
"Got a slice! {}-{}, step: {}, value: {}",
|
||||
index.start, index.stop, index.step, value
|
||||
|
@ -75,7 +76,8 @@ impl ExampleContainer {
|
|||
}
|
||||
}
|
||||
|
||||
#[pymodule(name = "getitem")]
|
||||
#[pymodule]
|
||||
#[pyo3(name = "getitem")]
|
||||
fn example(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
// ? -https://github.com/PyO3/maturin/issues/475
|
||||
m.add_class::<ExampleContainer>()?;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
variable::set("PYO3_VERSION", "0.23.0-dev");
|
||||
variable::set("PYO3_VERSION", "0.21.2");
|
||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||
file::rename(".template/pyproject.toml", "pyproject.toml");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
variable::set("PYO3_VERSION", "0.23.0-dev");
|
||||
variable::set("PYO3_VERSION", "0.21.2");
|
||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||
file::rename(".template/plugin_api/Cargo.toml", "plugin_api/Cargo.toml");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use core::sync::atomic::{AtomicU64, Ordering};
|
||||
use core::{mem, ptr};
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::{c_char, c_int, c_uint, c_ulonglong, c_void};
|
||||
|
||||
use pyo3_ffi::*;
|
||||
|
@ -28,10 +27,10 @@ unsafe extern "C" fn id_new(
|
|||
kwds: *mut PyObject,
|
||||
) -> *mut PyObject {
|
||||
if PyTuple_Size(args) != 0 || !kwds.is_null() {
|
||||
// We use pyo3-ffi's `c_str!` macro to create null-terminated literals because
|
||||
// Rust's string literals are not null-terminated
|
||||
// On Rust 1.77 or newer you can use `c"text"` instead.
|
||||
PyErr_SetString(PyExc_TypeError, c_str!("Id() takes no arguments").as_ptr());
|
||||
PyErr_SetString(
|
||||
PyExc_TypeError,
|
||||
"Id() takes no arguments\0".as_ptr().cast::<c_char>(),
|
||||
);
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
|
@ -82,12 +81,8 @@ unsafe extern "C" fn id_richcompare(
|
|||
pyo3_ffi::Py_GT => slf > other,
|
||||
pyo3_ffi::Py_GE => slf >= other,
|
||||
unrecognized => {
|
||||
let msg = CString::new(&*format!(
|
||||
"unrecognized richcompare opcode {}",
|
||||
unrecognized
|
||||
))
|
||||
.unwrap();
|
||||
PyErr_SetString(PyExc_SystemError, msg.as_ptr());
|
||||
let msg = format!("unrecognized richcompare opcode {}\0", unrecognized);
|
||||
PyErr_SetString(PyExc_SystemError, msg.as_ptr().cast::<c_char>());
|
||||
return ptr::null_mut();
|
||||
}
|
||||
};
|
||||
|
@ -106,7 +101,7 @@ static mut SLOTS: &[PyType_Slot] = &[
|
|||
},
|
||||
PyType_Slot {
|
||||
slot: Py_tp_doc,
|
||||
pfunc: c_str!("An id that is increased every time an instance is created").as_ptr()
|
||||
pfunc: "An id that is increased every time an instance is created\0".as_ptr()
|
||||
as *mut c_void,
|
||||
},
|
||||
PyType_Slot {
|
||||
|
@ -128,7 +123,7 @@ static mut SLOTS: &[PyType_Slot] = &[
|
|||
];
|
||||
|
||||
pub static mut ID_SPEC: PyType_Spec = PyType_Spec {
|
||||
name: c_str!("sequential.Id").as_ptr(),
|
||||
name: "sequential.Id\0".as_ptr().cast::<c_char>(),
|
||||
basicsize: mem::size_of::<PyId>() as c_int,
|
||||
itemsize: 0,
|
||||
flags: (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE) as c_uint,
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
use core::{mem, ptr};
|
||||
use pyo3_ffi::*;
|
||||
use std::os::raw::{c_int, c_void};
|
||||
use std::os::raw::{c_char, c_int, c_void};
|
||||
|
||||
pub static mut MODULE_DEF: PyModuleDef = PyModuleDef {
|
||||
m_base: PyModuleDef_HEAD_INIT,
|
||||
m_name: c_str!("sequential").as_ptr(),
|
||||
m_doc: c_str!("A library for generating sequential ids, written in Rust.").as_ptr(),
|
||||
m_name: "sequential\0".as_ptr().cast::<c_char>(),
|
||||
m_doc: "A library for generating sequential ids, written in Rust.\0"
|
||||
.as_ptr()
|
||||
.cast::<c_char>(),
|
||||
m_size: mem::size_of::<sequential_state>() as Py_ssize_t,
|
||||
m_methods: std::ptr::null_mut(),
|
||||
m_slots: unsafe { SEQUENTIAL_SLOTS as *const [PyModuleDef_Slot] as *mut PyModuleDef_Slot },
|
||||
|
@ -40,13 +42,13 @@ unsafe extern "C" fn sequential_exec(module: *mut PyObject) -> c_int {
|
|||
if id_type.is_null() {
|
||||
PyErr_SetString(
|
||||
PyExc_SystemError,
|
||||
c_str!("cannot locate type object").as_ptr(),
|
||||
"cannot locate type object\0".as_ptr().cast::<c_char>(),
|
||||
);
|
||||
return -1;
|
||||
}
|
||||
(*state).id_type = id_type.cast::<PyTypeObject>();
|
||||
|
||||
PyModule_AddObjectRef(module, c_str!("Id").as_ptr(), id_type)
|
||||
PyModule_AddObjectRef(module, "Id\0".as_ptr().cast::<c_char>(), id_type)
|
||||
}
|
||||
|
||||
unsafe extern "C" fn sequential_traverse(
|
||||
|
|
|
@ -5,13 +5,11 @@ use std::thread;
|
|||
use pyo3_ffi::*;
|
||||
use sequential::PyInit_sequential;
|
||||
|
||||
static COMMAND: &'static str = c_str!(
|
||||
"
|
||||
static COMMAND: &'static str = "
|
||||
from sequential import Id
|
||||
|
||||
s = sum(int(Id()) for _ in range(12))
|
||||
"
|
||||
);
|
||||
\0";
|
||||
|
||||
// Newtype to be able to pass it to another thread.
|
||||
struct State(*mut PyThreadState);
|
||||
|
@ -21,7 +19,10 @@ unsafe impl Send for State {}
|
|||
#[test]
|
||||
fn lets_go_fast() -> Result<(), String> {
|
||||
unsafe {
|
||||
let ret = PyImport_AppendInittab(c_str!("sequential").as_ptr(), Some(PyInit_sequential));
|
||||
let ret = PyImport_AppendInittab(
|
||||
"sequential\0".as_ptr().cast::<c_char>(),
|
||||
Some(PyInit_sequential),
|
||||
);
|
||||
if ret == -1 {
|
||||
return Err("could not add module to inittab".into());
|
||||
}
|
||||
|
@ -121,8 +122,11 @@ unsafe fn fetch() -> String {
|
|||
|
||||
fn run_code() -> Result<u64, String> {
|
||||
unsafe {
|
||||
let code_obj =
|
||||
Py_CompileString(COMMAND.as_ptr(), c_str!("program").as_ptr(), Py_file_input);
|
||||
let code_obj = Py_CompileString(
|
||||
COMMAND.as_ptr().cast::<c_char>(),
|
||||
"program\0".as_ptr().cast::<c_char>(),
|
||||
Py_file_input,
|
||||
);
|
||||
if code_obj.is_null() {
|
||||
return Err(fetch());
|
||||
}
|
||||
|
@ -134,7 +138,7 @@ fn run_code() -> Result<u64, String> {
|
|||
} else {
|
||||
Py_DECREF(res_ptr);
|
||||
}
|
||||
let sum = PyDict_GetItemString(globals, c_str!("s").as_ptr()); /* borrowed reference */
|
||||
let sum = PyDict_GetItemString(globals, "s\0".as_ptr().cast::<c_char>()); /* borrowed reference */
|
||||
if sum.is_null() {
|
||||
Py_DECREF(globals);
|
||||
return Err("globals did not have `s`".into());
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
variable::set("PYO3_VERSION", "0.23.0-dev");
|
||||
variable::set("PYO3_VERSION", "0.21.2");
|
||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||
file::rename(".template/setup.cfg", "setup.cfg");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -5,8 +5,10 @@ use pyo3_ffi::*;
|
|||
|
||||
static mut MODULE_DEF: PyModuleDef = PyModuleDef {
|
||||
m_base: PyModuleDef_HEAD_INIT,
|
||||
m_name: c_str!("string_sum").as_ptr(),
|
||||
m_doc: c_str!("A Python module written in Rust.").as_ptr(),
|
||||
m_name: "string_sum\0".as_ptr().cast::<c_char>(),
|
||||
m_doc: "A Python module written in Rust.\0"
|
||||
.as_ptr()
|
||||
.cast::<c_char>(),
|
||||
m_size: 0,
|
||||
m_methods: unsafe { METHODS as *const [PyMethodDef] as *mut PyMethodDef },
|
||||
m_slots: std::ptr::null_mut(),
|
||||
|
@ -17,12 +19,14 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef {
|
|||
|
||||
static mut METHODS: &[PyMethodDef] = &[
|
||||
PyMethodDef {
|
||||
ml_name: c_str!("sum_as_string").as_ptr(),
|
||||
ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(),
|
||||
ml_meth: PyMethodDefPointer {
|
||||
_PyCFunctionFast: sum_as_string,
|
||||
},
|
||||
ml_flags: METH_FASTCALL,
|
||||
ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
|
||||
ml_doc: "returns the sum of two integers as a string\0"
|
||||
.as_ptr()
|
||||
.cast::<c_char>(),
|
||||
},
|
||||
// A zeroed PyMethodDef to mark the end of the array.
|
||||
PyMethodDef::zeroed(),
|
||||
|
@ -89,7 +93,9 @@ pub unsafe extern "C" fn sum_as_string(
|
|||
if nargs != 2 {
|
||||
PyErr_SetString(
|
||||
PyExc_TypeError,
|
||||
c_str!("sum_as_string expected 2 positional arguments").as_ptr(),
|
||||
"sum_as_string expected 2 positional arguments\0"
|
||||
.as_ptr()
|
||||
.cast::<c_char>(),
|
||||
);
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
@ -113,7 +119,7 @@ pub unsafe extern "C" fn sum_as_string(
|
|||
None => {
|
||||
PyErr_SetString(
|
||||
PyExc_OverflowError,
|
||||
c_str!("arguments too large to add").as_ptr(),
|
||||
"arguments too large to add\0".as_ptr().cast::<c_char>(),
|
||||
);
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
variable::set("PYO3_VERSION", "0.23.0-dev");
|
||||
variable::set("PYO3_VERSION", "0.21.2");
|
||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||
file::rename(".template/pyproject.toml", "pyproject.toml");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -2,20 +2,15 @@
|
|||
|
||||
| Parameter | Description |
|
||||
| :- | :- |
|
||||
| `constructor` | This is currently only allowed on [variants of complex enums][params-constructor]. It allows customization of the generated class constructor for each variant. It uses the same syntax and supports the same options as the `signature` attribute of functions and methods. |
|
||||
| <span style="white-space: pre">`crate = "some::path"`</span> | Path to import the `pyo3` crate, if it's not accessible at `::pyo3`. |
|
||||
| `dict` | Gives instances of this class an empty `__dict__` to store custom attributes. |
|
||||
| `eq` | Implements `__eq__` using the `PartialEq` implementation of the underlying Rust datatype. |
|
||||
| `eq_int` | Implements `__eq__` using `__int__` for simple enums. |
|
||||
| <span style="white-space: pre">`extends = BaseType`</span> | Use a custom baseclass. Defaults to [`PyAny`][params-1] |
|
||||
| <span style="white-space: pre">`freelist = N`</span> | Implements a [free list][params-2] of size N. This can improve performance for types that are often created and deleted in quick succession. Profile your code to see whether `freelist` is right for you. |
|
||||
| <span style="white-space: pre">`frozen`</span> | Declares that your pyclass is immutable. It removes the borrow checker overhead when retrieving a shared reference to the Rust struct, but disables the ability to get a mutable reference. |
|
||||
| `get_all` | Generates getters for all fields of the pyclass. |
|
||||
| `hash` | Implements `__hash__` using the `Hash` implementation of the underlying Rust datatype. |
|
||||
| `mapping` | Inform PyO3 that this class is a [`Mapping`][params-mapping], and so leave its implementation of sequence C-API slots empty. |
|
||||
| <span style="white-space: pre">`module = "module_name"`</span> | Python code will see the class as being defined in this module. Defaults to `builtins`. |
|
||||
| <span style="white-space: pre">`name = "python_name"`</span> | Sets the name that Python sees this class as. Defaults to the name of the Rust struct. |
|
||||
| `ord` | Implements `__lt__`, `__gt__`, `__le__`, & `__ge__` using the `PartialOrd` implementation of the underlying Rust datatype. *Requires `eq`* |
|
||||
| `rename_all = "renaming_rule"` | Applies renaming rules to every getters and setters of a struct, or every variants of an enum. Possible values are: "camelCase", "kebab-case", "lowercase", "PascalCase", "SCREAMING-KEBAB-CASE", "SCREAMING_SNAKE_CASE", "snake_case", "UPPERCASE". |
|
||||
| `sequence` | Inform PyO3 that this class is a [`Sequence`][params-sequence], and so leave its C-API mapping length slot empty. |
|
||||
| `set_all` | Generates setters for all fields of the pyclass. |
|
||||
|
@ -44,6 +39,5 @@ struct MyClass {}
|
|||
[params-4]: https://doc.rust-lang.org/std/rc/struct.Rc.html
|
||||
[params-5]: https://doc.rust-lang.org/std/sync/struct.Arc.html
|
||||
[params-6]: https://docs.python.org/3/library/weakref.html
|
||||
[params-constructor]: https://pyo3.rs/latest/class.html#complex-enums
|
||||
[params-mapping]: https://pyo3.rs/latest/class/protocols.html#mapping--sequence-types
|
||||
[params-sequence]: https://pyo3.rs/latest/class/protocols.html#mapping--sequence-types
|
||||
|
|
|
@ -12,7 +12,6 @@ use futures::channel::oneshot;
|
|||
use pyo3::prelude::*;
|
||||
|
||||
#[pyfunction]
|
||||
#[pyo3(signature=(seconds, result=None))]
|
||||
async fn sleep(seconds: f64, result: Option<PyObject>) -> Option<PyObject> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
thread::spawn(move || {
|
||||
|
|
|
@ -151,7 +151,7 @@ rustflags = [
|
|||
]
|
||||
```
|
||||
|
||||
Alternatively, one can include in `build.rs`:
|
||||
Alternatively, on rust >= 1.56, one can include in `build.rs`:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
|
@ -163,7 +163,7 @@ fn main() {
|
|||
|
||||
For more discussion on and workarounds for MacOS linking problems [see this issue](https://github.com/PyO3/pyo3/issues/1800#issuecomment-906786649).
|
||||
|
||||
Finally, don't forget that on MacOS the `extension-module` feature will cause `cargo test` to fail without the `--no-default-features` flag (see [the FAQ](https://pyo3.rs/main/faq.html#i-cant-run-cargo-test-or-i-cant-build-in-a-cargo-workspace-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror)).
|
||||
Finally, don't forget that on MacOS the `extension-module` feature will cause `cargo test` to fail without the `--no-default-features` flag (see [the FAQ](https://pyo3.rs/main/faq.html#i-cant-run-cargo-test-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror)).
|
||||
|
||||
### The `extension-module` feature
|
||||
|
||||
|
@ -232,7 +232,7 @@ not work when compiling for `abi3`. These are:
|
|||
|
||||
If you want to embed the Python interpreter inside a Rust program, there are two modes in which this can be done: dynamically and statically. We'll cover each of these modes in the following sections. Each of them affect how you must distribute your program. Instead of learning how to do this yourself, you might want to consider using a project like [PyOxidizer] to ship your application and all of its dependencies in a single file.
|
||||
|
||||
PyO3 automatically switches between the two linking modes depending on whether the Python distribution you have configured PyO3 to use ([see above](#configuring-the-python-version)) contains a shared library or a static library. The static library is most often seen in Python distributions compiled from source without the `--enable-shared` configuration option.
|
||||
PyO3 automatically switches between the two linking modes depending on whether the Python distribution you have configured PyO3 to use ([see above](#configuring-the-python-version)) contains a shared library or a static library. The static library is most often seen in Python distributions compiled from source without the `--enable-shared` configuration option. For example, this is the default for `pyenv` on macOS.
|
||||
|
||||
### Dynamically embedding the Python interpreter
|
||||
|
||||
|
|
|
@ -37,16 +37,14 @@ struct Number(i32);
|
|||
|
||||
// PyO3 supports unit-only enums (which contain only unit variants)
|
||||
// These simple enums behave similarly to Python's enumerations (enum.Enum)
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
#[pyclass]
|
||||
enum MyEnum {
|
||||
Variant,
|
||||
OtherVariant = 30, // PyO3 supports custom discriminants.
|
||||
}
|
||||
|
||||
// PyO3 supports custom discriminants in unit-only enums
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
#[pyclass]
|
||||
enum HttpResponse {
|
||||
Ok = 200,
|
||||
NotFound = 404,
|
||||
|
@ -54,18 +52,15 @@ enum HttpResponse {
|
|||
// ...
|
||||
}
|
||||
|
||||
// PyO3 also supports enums with Struct and Tuple variants
|
||||
// PyO3 also supports enums with non-unit variants
|
||||
// These complex enums have sligtly different behavior from the simple enums above
|
||||
// They are meant to work with instance checks and match statement patterns
|
||||
// The variants can be mixed and matched
|
||||
// Struct variants have named fields while tuple enums generate generic names for fields in order _0, _1, _2, ...
|
||||
// Apart from this both types are functionally identical
|
||||
#[pyclass]
|
||||
enum Shape {
|
||||
Circle { radius: f64 },
|
||||
Rectangle { width: f64, height: f64 },
|
||||
RegularPolygon(u32, f64),
|
||||
Nothing(),
|
||||
RegularPolygon { side_count: u32, radius: f64 },
|
||||
Nothing {},
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -254,7 +249,7 @@ fn return_myclass() -> Py<MyClass> {
|
|||
|
||||
let obj = return_myclass();
|
||||
|
||||
Python::with_gil(move |py| {
|
||||
Python::with_gil(|py| {
|
||||
let bound = obj.bind(py); // Py<MyClass>::bind returns &Bound<'py, MyClass>
|
||||
let obj_ref = bound.borrow(); // Get PyRef<T>
|
||||
assert_eq!(obj_ref.num, 1);
|
||||
|
@ -285,8 +280,6 @@ let py_counter: Py<FrozenCounter> = Python::with_gil(|py| {
|
|||
});
|
||||
|
||||
py_counter.get().value.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
Python::with_gil(move |_py| drop(py_counter));
|
||||
```
|
||||
|
||||
Frozen classes are likely to become the default thereby guiding the PyO3 ecosystem towards a more deliberate application of interior mutability. Eventually, this should enable further optimizations of PyO3's internals and avoid downstream code paying the cost of interior mutability when it is not actually required.
|
||||
|
@ -327,12 +320,8 @@ explicitly.
|
|||
|
||||
To get a parent class from a child, use [`PyRef`] instead of `&self` for methods,
|
||||
or [`PyRefMut`] instead of `&mut self`.
|
||||
Then you can access a parent class by `self_.as_super()` as `&PyRef<Self::BaseClass>`,
|
||||
or by `self_.into_super()` as `PyRef<Self::BaseClass>` (and similar for the `PyRefMut`
|
||||
case). For convenience, `self_.as_ref()` can also be used to get `&Self::BaseClass`
|
||||
directly; however, this approach does not let you access base clases higher in the
|
||||
inheritance hierarchy, for which you would need to chain multiple `as_super` or
|
||||
`into_super` calls.
|
||||
Then you can access a parent class by `self_.as_ref()` as `&Self::BaseClass`,
|
||||
or by `self_.into_super()` as `PyRef<Self::BaseClass>`.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
|
@ -349,7 +338,7 @@ impl BaseClass {
|
|||
BaseClass { val1: 10 }
|
||||
}
|
||||
|
||||
pub fn method1(&self) -> PyResult<usize> {
|
||||
pub fn method(&self) -> PyResult<usize> {
|
||||
Ok(self.val1)
|
||||
}
|
||||
}
|
||||
|
@ -367,8 +356,8 @@ impl SubClass {
|
|||
}
|
||||
|
||||
fn method2(self_: PyRef<'_, Self>) -> PyResult<usize> {
|
||||
let super_ = self_.as_super(); // Get &PyRef<BaseClass>
|
||||
super_.method1().map(|x| x * self_.val2)
|
||||
let super_ = self_.as_ref(); // Get &BaseClass
|
||||
super_.method().map(|x| x * self_.val2)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -385,28 +374,11 @@ impl SubSubClass {
|
|||
}
|
||||
|
||||
fn method3(self_: PyRef<'_, Self>) -> PyResult<usize> {
|
||||
let base = self_.as_super().as_super(); // Get &PyRef<'_, BaseClass>
|
||||
base.method1().map(|x| x * self_.val3)
|
||||
}
|
||||
|
||||
fn method4(self_: PyRef<'_, Self>) -> PyResult<usize> {
|
||||
let v = self_.val3;
|
||||
let super_ = self_.into_super(); // Get PyRef<'_, SubClass>
|
||||
SubClass::method2(super_).map(|x| x * v)
|
||||
}
|
||||
|
||||
fn get_values(self_: PyRef<'_, Self>) -> (usize, usize, usize) {
|
||||
let val1 = self_.as_super().as_super().val1;
|
||||
let val2 = self_.as_super().val2;
|
||||
(val1, val2, self_.val3)
|
||||
}
|
||||
|
||||
fn double_values(mut self_: PyRefMut<'_, Self>) {
|
||||
self_.as_super().as_super().val1 *= 2;
|
||||
self_.as_super().val2 *= 2;
|
||||
self_.val3 *= 2;
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn factory_method(py: Python<'_>, val: usize) -> PyResult<PyObject> {
|
||||
let base = PyClassInitializer::from(BaseClass::new());
|
||||
|
@ -421,13 +393,7 @@ impl SubSubClass {
|
|||
}
|
||||
# Python::with_gil(|py| {
|
||||
# let subsub = pyo3::Py::new(py, SubSubClass::new()).unwrap();
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.method1() == 10");
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.method2() == 150");
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.method3() == 200");
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.method4() == 3000");
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.get_values() == (10, 15, 20)");
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.double_values() == None");
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.get_values() == (20, 30, 40)");
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.method3() == 3000");
|
||||
# let subsub = SubSubClass::factory_method(py, 2).unwrap();
|
||||
# let subsubsub = SubSubClass::factory_method(py, 3).unwrap();
|
||||
# let cls = py.get_type_bound::<SubSubClass>();
|
||||
|
@ -1082,8 +1048,7 @@ PyO3 adds a class attribute for each variant, so you can access them in Python w
|
|||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
#[pyclass]
|
||||
enum MyEnum {
|
||||
Variant,
|
||||
OtherVariant,
|
||||
|
@ -1105,8 +1070,7 @@ You can also convert your simple enums into `int`:
|
|||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
#[pyclass]
|
||||
enum MyEnum {
|
||||
Variant,
|
||||
OtherVariant = 10,
|
||||
|
@ -1118,6 +1082,8 @@ Python::with_gil(|py| {
|
|||
pyo3::py_run!(py, cls x, r#"
|
||||
assert int(cls.Variant) == x
|
||||
assert int(cls.OtherVariant) == 10
|
||||
assert cls.OtherVariant == 10 # You can also compare against int.
|
||||
assert 10 == cls.OtherVariant
|
||||
"#)
|
||||
})
|
||||
```
|
||||
|
@ -1126,8 +1092,7 @@ PyO3 also provides `__repr__` for enums:
|
|||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
#[pyclass]
|
||||
enum MyEnum{
|
||||
Variant,
|
||||
OtherVariant,
|
||||
|
@ -1147,8 +1112,7 @@ All methods defined by PyO3 can be overridden. For example here's how you overri
|
|||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
#[pyclass]
|
||||
enum MyEnum {
|
||||
Answer = 42,
|
||||
}
|
||||
|
@ -1170,8 +1134,7 @@ Enums and their variants can also be renamed using `#[pyo3(name)]`.
|
|||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass(eq, eq_int, name = "RenamedEnum")]
|
||||
#[derive(PartialEq)]
|
||||
#[pyclass(name = "RenamedEnum")]
|
||||
enum MyEnum {
|
||||
#[pyo3(name = "UPPERCASE")]
|
||||
Variant,
|
||||
|
@ -1187,32 +1150,6 @@ Python::with_gil(|py| {
|
|||
})
|
||||
```
|
||||
|
||||
Ordering of enum variants is optionally added using `#[pyo3(ord)]`.
|
||||
*Note: Implementation of the `PartialOrd` trait is required when passing the `ord` argument. If not implemented, a compile time error is raised.*
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass(eq, ord)]
|
||||
#[derive(PartialEq, PartialOrd)]
|
||||
enum MyEnum{
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let cls = py.get_type_bound::<MyEnum>();
|
||||
let a = Py::new(py, MyEnum::A).unwrap();
|
||||
let b = Py::new(py, MyEnum::B).unwrap();
|
||||
let c = Py::new(py, MyEnum::C).unwrap();
|
||||
pyo3::py_run!(py, cls a b c, r#"
|
||||
assert (a < b) == True
|
||||
assert (c <= b) == False
|
||||
assert (c > a) == True
|
||||
"#)
|
||||
})
|
||||
```
|
||||
|
||||
You may not use enums as a base class or let enums inherit from other classes.
|
||||
|
||||
```rust,compile_fail
|
||||
|
@ -1241,7 +1178,7 @@ enum BadSubclass {
|
|||
|
||||
An enum is complex if it has any non-unit (struct or tuple) variants.
|
||||
|
||||
PyO3 supports only struct and tuple variants in a complex enum. Unit variants aren't supported at present (the recommendation is to use an empty tuple enum instead).
|
||||
Currently PyO3 supports only struct variants in a complex enum. Support for unit and tuple variants is planned.
|
||||
|
||||
PyO3 adds a class attribute for each variant, which may be used to construct values and in match patterns. PyO3 also provides getter methods for all fields of each variant.
|
||||
|
||||
|
@ -1251,14 +1188,14 @@ PyO3 adds a class attribute for each variant, which may be used to construct val
|
|||
enum Shape {
|
||||
Circle { radius: f64 },
|
||||
Rectangle { width: f64, height: f64 },
|
||||
RegularPolygon(u32, f64),
|
||||
RegularPolygon { side_count: u32, radius: f64 },
|
||||
Nothing { },
|
||||
}
|
||||
|
||||
# #[cfg(Py_3_10)]
|
||||
Python::with_gil(|py| {
|
||||
let circle = Shape::Circle { radius: 10.0 }.into_py(py);
|
||||
let square = Shape::RegularPolygon(4, 10.0).into_py(py);
|
||||
let square = Shape::RegularPolygon { side_count: 4, radius: 10.0 }.into_py(py);
|
||||
let cls = py.get_type_bound::<Shape>();
|
||||
pyo3::py_run!(py, circle square cls, r#"
|
||||
assert isinstance(circle, cls)
|
||||
|
@ -1267,8 +1204,8 @@ Python::with_gil(|py| {
|
|||
|
||||
assert isinstance(square, cls)
|
||||
assert isinstance(square, cls.RegularPolygon)
|
||||
assert square[0] == 4 # Gets _0 field
|
||||
assert square[1] == 10.0 # Gets _1 field
|
||||
assert square.side_count == 4
|
||||
assert square.radius == 10.0
|
||||
|
||||
def count_vertices(cls, shape):
|
||||
match shape:
|
||||
|
@ -1276,7 +1213,7 @@ Python::with_gil(|py| {
|
|||
return 0
|
||||
case cls.Rectangle():
|
||||
return 4
|
||||
case cls.RegularPolygon(n):
|
||||
case cls.RegularPolygon(side_count=n):
|
||||
return n
|
||||
case cls.Nothing():
|
||||
return 0
|
||||
|
@ -1306,46 +1243,6 @@ Python::with_gil(|py| {
|
|||
})
|
||||
```
|
||||
|
||||
The constructor of each generated class can be customized using the `#[pyo3(constructor = (...))]` attribute. This uses the same syntax as the [`#[pyo3(signature = (...))]`](function/signature.md)
|
||||
attribute on function and methods and supports the same options. To apply this attribute simply place it on top of a variant in a `#[pyclass]` complex enum as shown below:
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
enum Shape {
|
||||
#[pyo3(constructor = (radius=1.0))]
|
||||
Circle { radius: f64 },
|
||||
#[pyo3(constructor = (*, width, height))]
|
||||
Rectangle { width: f64, height: f64 },
|
||||
#[pyo3(constructor = (side_count, radius=1.0))]
|
||||
RegularPolygon { side_count: u32, radius: f64 },
|
||||
Nothing { },
|
||||
}
|
||||
|
||||
# #[cfg(Py_3_10)]
|
||||
Python::with_gil(|py| {
|
||||
let cls = py.get_type_bound::<Shape>();
|
||||
pyo3::py_run!(py, cls, r#"
|
||||
circle = cls.Circle()
|
||||
assert isinstance(circle, cls)
|
||||
assert isinstance(circle, cls.Circle)
|
||||
assert circle.radius == 1.0
|
||||
|
||||
square = cls.Rectangle(width = 1, height = 1)
|
||||
assert isinstance(square, cls)
|
||||
assert isinstance(square, cls.Rectangle)
|
||||
assert square.width == 1
|
||||
assert square.height == 1
|
||||
|
||||
hexagon = cls.RegularPolygon(6)
|
||||
assert isinstance(hexagon, cls)
|
||||
assert isinstance(hexagon, cls.RegularPolygon)
|
||||
assert hexagon.side_count == 6
|
||||
assert hexagon.radius == 1
|
||||
"#)
|
||||
})
|
||||
```
|
||||
|
||||
## Implementation details
|
||||
|
||||
The `#[pyclass]` macros rely on a lot of conditional code generation: each `#[pyclass]` can optionally have a `#[pymethods]` block.
|
||||
|
@ -1368,7 +1265,6 @@ struct MyClass {
|
|||
impl pyo3::types::DerefToPyAny for MyClass {}
|
||||
|
||||
# #[allow(deprecated)]
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
unsafe impl pyo3::type_object::HasPyGilRef for MyClass {
|
||||
type AsRefTarget = pyo3::PyCell<Self>;
|
||||
}
|
||||
|
@ -1443,7 +1339,7 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
|
|||
static DOC: pyo3::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = pyo3::sync::GILOnceCell::new();
|
||||
DOC.get_or_try_init(py, || {
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, pyo3::ffi::c_str!(""), collector.new_text_signature())
|
||||
build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, "\0", collector.new_text_signature())
|
||||
}).map(::std::ops::Deref::deref)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -210,7 +210,7 @@ use std::hash::{Hash, Hasher};
|
|||
use pyo3::exceptions::{PyValueError, PyZeroDivisionError};
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::class::basic::CompareOp;
|
||||
use pyo3::types::{PyComplex, PyString};
|
||||
use pyo3::types::PyComplex;
|
||||
|
||||
fn wrap(obj: &Bound<'_, PyAny>) -> PyResult<i32> {
|
||||
let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
|
||||
|
@ -231,7 +231,7 @@ impl Number {
|
|||
|
||||
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
|
||||
// Get the class name dynamically in case `Number` is subclassed
|
||||
let class_name: Bound<'_, PyString> = slf.get_type().qualname()?;
|
||||
let class_name: String = slf.get_type().qualname()?;
|
||||
Ok(format!("{}({})", class_name, slf.borrow().0))
|
||||
}
|
||||
|
||||
|
|
|
@ -80,7 +80,6 @@ the subclass name. This is typically done in Python code by accessing
|
|||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
#
|
||||
# #[pyclass]
|
||||
# struct Number(i32);
|
||||
|
@ -89,7 +88,7 @@ the subclass name. This is typically done in Python code by accessing
|
|||
impl Number {
|
||||
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
|
||||
// This is the equivalent of `self.__class__.__name__` in Python.
|
||||
let class_name: Bound<'_, PyString> = slf.get_type().qualname()?;
|
||||
let class_name: String = slf.get_type().qualname()?;
|
||||
// To access fields of the Rust struct, we need to borrow the `PyCell`.
|
||||
Ok(format!("{}({})", class_name, slf.borrow().0))
|
||||
}
|
||||
|
@ -122,19 +121,6 @@ impl Number {
|
|||
}
|
||||
}
|
||||
```
|
||||
To implement `__hash__` using the Rust [`Hash`] trait implementation, the `hash` option can be used.
|
||||
This option is only available for `frozen` classes to prevent accidental hash changes from mutating the object. If you need
|
||||
an `__hash__` implementation for a mutable class, use the manual method from above. This option also requires `eq`: According to the
|
||||
[Python docs](https://docs.python.org/3/reference/datamodel.html#object.__hash__) "If a class does not define an `__eq__()`
|
||||
method it should not define a `__hash__()` operation either"
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#
|
||||
#[pyclass(frozen, eq, hash)]
|
||||
#[derive(PartialEq, Hash)]
|
||||
struct Number(i32);
|
||||
```
|
||||
|
||||
|
||||
> **Note**: When implementing `__hash__` and comparisons, it is important that the following property holds:
|
||||
>
|
||||
|
@ -240,26 +226,6 @@ impl Number {
|
|||
# }
|
||||
```
|
||||
|
||||
To implement `__eq__` using the Rust [`PartialEq`] trait implementation, the `eq` option can be used.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#
|
||||
#[pyclass(eq)]
|
||||
#[derive(PartialEq)]
|
||||
struct Number(i32);
|
||||
```
|
||||
|
||||
To implement `__lt__`, `__le__`, `__gt__`, & `__ge__` using the Rust `PartialOrd` trait implementation, the `ord` option can be used. *Note: Requires `eq`.*
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#
|
||||
#[pyclass(eq, ord)]
|
||||
#[derive(PartialEq, PartialOrd)]
|
||||
struct Number(i32);
|
||||
```
|
||||
|
||||
### Truthyness
|
||||
|
||||
We'll consider `Number` to be `True` if it is nonzero:
|
||||
|
@ -286,7 +252,6 @@ use std::hash::{Hash, Hasher};
|
|||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::class::basic::CompareOp;
|
||||
use pyo3::types::PyString;
|
||||
|
||||
#[pyclass]
|
||||
struct Number(i32);
|
||||
|
@ -299,7 +264,7 @@ impl Number {
|
|||
}
|
||||
|
||||
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
|
||||
let class_name: Bound<'_, PyString> = slf.get_type().qualname()?;
|
||||
let class_name: String = slf.get_type().qualname()?;
|
||||
Ok(format!("{}({})", class_name, slf.borrow().0))
|
||||
}
|
||||
|
||||
|
@ -340,4 +305,3 @@ fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|||
[`Hasher`]: https://doc.rust-lang.org/std/hash/trait.Hasher.html
|
||||
[`DefaultHasher`]: https://doc.rust-lang.org/std/collections/hash_map/struct.DefaultHasher.html
|
||||
[SipHash]: https://en.wikipedia.org/wiki/SipHash
|
||||
[`PartialEq`]: https://doc.rust-lang.org/stable/std/cmp/trait.PartialEq.html
|
||||
|
|
|
@ -1,27 +1,20 @@
|
|||
# Class customizations
|
||||
# Magic methods and slots
|
||||
|
||||
Python's object model defines several protocols for different object behavior, such as the sequence, mapping, and number protocols. Python classes support these protocols by implementing "magic" methods, such as `__str__` or `__repr__`. Because of the double-underscores surrounding their name, these are also known as "dunder" methods.
|
||||
Python's object model defines several protocols for different object behavior, such as the sequence, mapping, and number protocols. You may be familiar with implementing these protocols in Python classes by "magic" methods, such as `__str__` or `__repr__`. Because of the double-underscores surrounding their name, these are also known as "dunder" methods.
|
||||
|
||||
PyO3 makes it possible for every magic method to be implemented in `#[pymethods]` just as they would be done in a regular Python class, with a few notable differences:
|
||||
- `__new__` and `__init__` are replaced by the [`#[new]` attribute](../class.md#constructor).
|
||||
- `__del__` is not yet supported, but may be in the future.
|
||||
- `__buffer__` and `__release_buffer__` are currently not supported and instead PyO3 supports [`__getbuffer__` and `__releasebuffer__`](#buffer-objects) methods (these predate [PEP 688](https://peps.python.org/pep-0688/#python-level-buffer-protocol)), again this may change in the future.
|
||||
- PyO3 adds [`__traverse__` and `__clear__`](#garbage-collector-integration) methods for controlling garbage collection.
|
||||
- The Python C-API which PyO3 is implemented upon requires many magic methods to have a specific function signature in C and be placed into special "slots" on the class type object. This limits the allowed argument and return types for these methods. They are listed in detail in the section below.
|
||||
In the Python C-API which PyO3 is implemented upon, many of these magic methods have to be placed into special "slots" on the class type object, as covered in the previous section.
|
||||
|
||||
If a magic method is not on the list above (for example `__init_subclass__`), then it should just work in PyO3. If this is not the case, please file a bug report.
|
||||
If a function name in `#[pymethods]` is a recognised magic method, it will be automatically placed into the correct slot in the Python type object. The function name is taken from the usual rules for naming `#[pymethods]`: the `#[pyo3(name = "...")]` attribute is used if present, otherwise the Rust function name is used.
|
||||
|
||||
## Magic Methods handled by PyO3
|
||||
|
||||
If a function name in `#[pymethods]` is a magic method which is known to need special handling, it will be automatically placed into the correct slot in the Python type object. The function name is taken from the usual rules for naming `#[pymethods]`: the `#[pyo3(name = "...")]` attribute is used if present, otherwise the Rust function name is used.
|
||||
|
||||
The magic methods handled by PyO3 are very similar to the standard Python ones on [this page](https://docs.python.org/3/reference/datamodel.html#special-method-names) - in particular they are the subset which have slots as [defined here](https://docs.python.org/3/c-api/typeobj.html).
|
||||
The magic methods handled by PyO3 are very similar to the standard Python ones on [this page](https://docs.python.org/3/reference/datamodel.html#special-method-names) - in particular they are the subset which have slots as [defined here](https://docs.python.org/3/c-api/typeobj.html). Some of the slots do not have a magic method in Python, which leads to a few additional magic methods defined only in PyO3:
|
||||
- Magic methods for garbage collection
|
||||
- Magic methods for the buffer protocol
|
||||
|
||||
When PyO3 handles a magic method, a couple of changes apply compared to other `#[pymethods]`:
|
||||
- The Rust function signature is restricted to match the magic method.
|
||||
- The `#[pyo3(signature = (...)]` and `#[pyo3(text_signature = "...")]` attributes are not allowed.
|
||||
|
||||
The following sections list all magic methods for which PyO3 implements the necessary special handling. The
|
||||
The following sections list of all magic methods PyO3 currently handles. The
|
||||
given signatures should be interpreted as follows:
|
||||
- All methods take a receiver as first argument, shown as `<self>`. It can be
|
||||
`&self`, `&mut self` or a `Bound` reference like `self_: PyRef<'_, Self>` and
|
||||
|
@ -38,6 +31,7 @@ given signatures should be interpreted as follows:
|
|||
checked by the Python interpreter. For example, `__str__` needs to return a
|
||||
string object. This is indicated by `object (Python type)`.
|
||||
|
||||
|
||||
### Basic object customization
|
||||
|
||||
- `__str__(<self>) -> object (str)`
|
||||
|
|
|
@ -19,7 +19,6 @@ The table below contains the Python type and the corresponding function argument
|
|||
| `int` | `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize`, `num_bigint::BigInt`[^1], `num_bigint::BigUint`[^1] | `PyLong` |
|
||||
| `float` | `f32`, `f64` | `PyFloat` |
|
||||
| `complex` | `num_complex::Complex`[^2] | `PyComplex` |
|
||||
| `fractions.Fraction`| `num_rational::Ratio`[^8] | - |
|
||||
| `list[T]` | `Vec<T>` | `PyList` |
|
||||
| `dict[K, V]` | `HashMap<K, V>`, `BTreeMap<K, V>`, `hashbrown::HashMap<K, V>`[^3], `indexmap::IndexMap<K, V>`[^4] | `PyDict` |
|
||||
| `tuple[T, U]` | `(T, U)`, `Vec<T>` | `PyTuple` |
|
||||
|
@ -114,5 +113,3 @@ Finally, the following Rust types are also able to convert to Python as return v
|
|||
[^6]: Requires the `chrono-tz` optional feature.
|
||||
|
||||
[^7]: Requires the `rust_decimal` optional feature.
|
||||
|
||||
[^8]: Requires the `num-rational` optional feature.
|
||||
|
|
|
@ -265,7 +265,7 @@ use pyo3::prelude::*;
|
|||
|
||||
#[derive(FromPyObject)]
|
||||
# #[derive(Debug)]
|
||||
enum RustyEnum<'py> {
|
||||
enum RustyEnum<'a> {
|
||||
Int(usize), // input is a positive int
|
||||
String(String), // input is a string
|
||||
IntTuple(usize, usize), // input is a 2-tuple with positive ints
|
||||
|
@ -284,7 +284,7 @@ enum RustyEnum<'py> {
|
|||
b: usize,
|
||||
},
|
||||
#[pyo3(transparent)]
|
||||
CatchAll(Bound<'py, PyAny>), // This extraction never fails
|
||||
CatchAll(&'a PyAny), // This extraction never fails
|
||||
}
|
||||
#
|
||||
# use pyo3::types::{PyBytes, PyString};
|
||||
|
@ -394,7 +394,7 @@ enum RustyEnum<'py> {
|
|||
# assert_eq!(
|
||||
# b"text",
|
||||
# match rust_thing {
|
||||
# RustyEnum::CatchAll(ref i) => i.downcast::<PyBytes>()?.as_bytes(),
|
||||
# RustyEnum::CatchAll(i) => i.downcast::<PyBytes>()?.as_bytes(),
|
||||
# other => unreachable!("Error extracting: {:?}", other),
|
||||
# }
|
||||
# );
|
||||
|
|
|
@ -129,7 +129,9 @@ fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>> {
|
|||
|
||||
#[pymodule]
|
||||
fn my_async_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(rust_sleep, m)?)
|
||||
m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -150,7 +152,8 @@ fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>>> {
|
|||
|
||||
#[pymodule]
|
||||
fn my_async_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(rust_sleep, m)?)
|
||||
m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -254,7 +257,7 @@ async def py_sleep():
|
|||
await_coro(py_sleep())
|
||||
```
|
||||
|
||||
If you wanted to pass a callable function to the `#[pyfunction]` instead, (i.e. the last line becomes `await_coro(py_sleep))`, then the above example needs to be tweaked to first call the callable to get the coroutine:
|
||||
If for you wanted to pass a callable function to the `#[pyfunction]` instead, (i.e. the last line becomes `await_coro(py_sleep))`, then the above example needs to be tweaked to first call the callable to get the coroutine:
|
||||
|
||||
```rust
|
||||
#[pyfunction]
|
||||
|
|
|
@ -128,5 +128,5 @@ defines exceptions for several standard library modules.
|
|||
[`PyErr`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html
|
||||
[`PyResult`]: {{#PYO3_DOCS_URL}}/pyo3/type.PyResult.html
|
||||
[`PyErr::from_value`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html#method.from_value
|
||||
[`PyAny::is_instance`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.is_instance
|
||||
[`PyAny::is_instance_of`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.is_instance_of
|
||||
[`PyAny::is_instance`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html#method.is_instance
|
||||
[`PyAny::is_instance_of`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html#method.is_instance_of
|
||||
|
|
|
@ -127,10 +127,12 @@ If you don't want that cloning to happen, a workaround is to allocate the field
|
|||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
#[derive(Clone)]
|
||||
struct Inner {/* fields omitted */}
|
||||
|
||||
#[pyclass]
|
||||
struct Outer {
|
||||
#[pyo3(get)]
|
||||
inner: Py<Inner>,
|
||||
}
|
||||
|
||||
|
@ -142,11 +144,6 @@ impl Outer {
|
|||
inner: Py::new(py, Inner {})?,
|
||||
})
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn inner(&self, py: Python<'_>) -> Py<Inner> {
|
||||
self.inner.clone_ref(py)
|
||||
}
|
||||
}
|
||||
```
|
||||
This time `a` and `b` *are* the same object:
|
||||
|
|
|
@ -57,6 +57,12 @@ This feature adds support for `async fn` in `#[pyfunction]` and `#[pymethods]`.
|
|||
|
||||
The feature has some unfinished refinements and performance improvements. To help finish this off, see [issue #1632](https://github.com/PyO3/pyo3/issues/1632) and its associated draft PRs.
|
||||
|
||||
### `experimental-declarative-modules`
|
||||
|
||||
This feature allows to declare Python modules using `#[pymodule] mod my_module { ... }` syntax.
|
||||
|
||||
The feature has some unfinished refinements and edge cases. To help finish this off, see [issue #3900](https://github.com/PyO3/pyo3/issues/3900).
|
||||
|
||||
### `experimental-inspect`
|
||||
|
||||
This feature adds the `pyo3::inspect` module, as well as `IntoPy::type_output` and `FromPyObject::type_input` APIs to produce Python type "annotations" for Rust types.
|
||||
|
@ -69,14 +75,6 @@ This feature is a backwards-compatibility feature to allow continued use of the
|
|||
|
||||
This feature and the APIs it enables is expected to be removed in a future PyO3 version.
|
||||
|
||||
### `py-clone`
|
||||
|
||||
This feature was introduced to ease migration. It was found that delayed reference counts cannot be made sound and hence `Clon`ing an instance of `Py<T>` must panic without the GIL being held. To avoid migrations introducing new panics without warning, the `Clone` implementation itself is now gated behind this feature.
|
||||
|
||||
### `pyo3_disable_reference_pool`
|
||||
|
||||
This is a performance-oriented conditional compilation flag, e.g. [set via `$RUSTFLAGS`][set-configuration-options], which disabled the global reference pool and the assocaited overhead for the crossing the Python-Rust boundary. However, if enabled, `Drop`ping an instance of `Py<T>` without the GIL being held will abort the process.
|
||||
|
||||
### `macros`
|
||||
|
||||
This feature enables a dependency on the `pyo3-macros` crate, which provides the procedural macros portion of PyO3's API:
|
||||
|
@ -95,9 +93,9 @@ These macros require a number of dependencies which may not be needed by users w
|
|||
|
||||
### `multiple-pymethods`
|
||||
|
||||
This feature enables each `#[pyclass]` to have more than one `#[pymethods]` block.
|
||||
This feature enables a dependency on `inventory`, which enables each `#[pyclass]` to have more than one `#[pymethods]` block. This feature also requires a minimum Rust version of 1.62 due to limitations in the `inventory` crate.
|
||||
|
||||
Most users should only need a single `#[pymethods]` per `#[pyclass]`. In addition, not all platforms (e.g. Wasm) are supported by `inventory`, which is used in the implementation of the feature. For this reason this feature is not enabled by default, meaning fewer dependencies and faster compilation for the majority of users.
|
||||
Most users should only need a single `#[pymethods]` per `#[pyclass]`. In addition, not all platforms (e.g. Wasm) are supported by `inventory`. For this reason this feature is not enabled by default, meaning fewer dependencies and faster compilation for the majority of users.
|
||||
|
||||
See [the `#[pyclass]` implementation details](class.md#implementation-details) for more information.
|
||||
|
||||
|
@ -159,10 +157,6 @@ Adds a dependency on [num-bigint](https://docs.rs/num-bigint) and enables conver
|
|||
|
||||
Adds a dependency on [num-complex](https://docs.rs/num-complex) and enables conversions into its [`Complex`](https://docs.rs/num-complex/latest/num_complex/struct.Complex.html) type.
|
||||
|
||||
### `num-rational`
|
||||
|
||||
Adds a dependency on [num-rational](https://docs.rs/num-rational) and enables conversions into its [`Ratio`](https://docs.rs/num-rational/latest/num_rational/struct.Ratio.html) type.
|
||||
|
||||
### `rust_decimal`
|
||||
|
||||
Adds a dependency on [rust_decimal](https://docs.rs/rust_decimal) and enables conversions into its [`Decimal`](https://docs.rs/rust_decimal/latest/rust_decimal/struct.Decimal.html) type.
|
||||
|
@ -197,5 +191,3 @@ struct User {
|
|||
### `smallvec`
|
||||
|
||||
Adds a dependency on [smallvec](https://docs.rs/smallvec) and enables conversions into its [`SmallVec`](https://docs.rs/smallvec/latest/smallvec/struct.SmallVec.html) type.
|
||||
|
||||
[set-configuration-options]: https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options
|
||||
|
|
|
@ -14,7 +14,8 @@ fn double(x: usize) -> usize {
|
|||
|
||||
#[pymodule]
|
||||
fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(double, m)?)
|
||||
m.add_function(wrap_pyfunction!(double, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -55,7 +56,8 @@ The `#[pyo3]` attribute can be used to modify properties of the generated Python
|
|||
|
||||
#[pymodule]
|
||||
fn module_with_functions(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(no_args_py, m)?)
|
||||
m.add_function(wrap_pyfunction!(no_args_py, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
# Python::with_gil(|py| {
|
||||
|
@ -111,7 +113,8 @@ The `#[pyo3]` attribute can be used on individual arguments to modify properties
|
|||
use pyo3::prelude::*;
|
||||
|
||||
fn get_length(obj: &Bound<'_, PyAny>) -> PyResult<usize> {
|
||||
obj.len()
|
||||
let length = obj.len()?;
|
||||
Ok(length)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
|
@ -201,7 +204,8 @@ fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|||
x * 2
|
||||
}
|
||||
|
||||
m.add_function(wrap_pyfunction!(double, m)?)
|
||||
m.add_function(wrap_pyfunction!(double, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -22,7 +22,8 @@ fn num_kwds(kwds: Option<&Bound<'_, PyDict>>) -> usize {
|
|||
|
||||
#[pymodule]
|
||||
fn module_with_functions(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(num_kwds, m)?)
|
||||
m.add_function(wrap_pyfunction!(num_kwds, m)?).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -120,22 +121,9 @@ num=-1
|
|||
|
||||
## Trailing optional arguments
|
||||
|
||||
<div class="warning">
|
||||
|
||||
⚠️ Warning: This behaviour is being phased out 🛠️
|
||||
|
||||
The special casing of trailing optional arguments is deprecated. In a future `pyo3` version, arguments of type `Option<..>` will share the same behaviour as other arguments, they are required unless a default is set using `#[pyo3(signature = (...))]`.
|
||||
|
||||
This is done to better align the Python and Rust definition of such functions and make it more intuitive to rewrite them from Python in Rust. Specifically `def some_fn(a: int, b: Optional[int]): ...` will not automatically default `b` to `none`, but requires an explicit default if desired, where as in current `pyo3` it is handled the other way around.
|
||||
|
||||
During the migration window a `#[pyo3(signature = (...))]` will be required to silence the deprecation warning. After support for trailing optional arguments is fully removed, the signature attribute can be removed if all arguments should be required.
|
||||
</div>
|
||||
|
||||
|
||||
As a convenience, functions without a `#[pyo3(signature = (...))]` option will treat trailing `Option<T>` arguments as having a default of `None`. In the example below, PyO3 will create `increment` with a signature of `increment(x, amount=None)`.
|
||||
|
||||
```rust
|
||||
#![allow(deprecated)]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
/// Returns a copy of `x` increased by `amount`.
|
||||
|
|
|
@ -6,7 +6,7 @@ To get started using PyO3 you will need three things: a Rust toolchain, a Python
|
|||
|
||||
## Rust
|
||||
|
||||
First, make sure you have Rust installed on your system. If you haven't already done so, try following the instructions [here](https://www.rust-lang.org/tools/install). PyO3 runs on both the `stable` and `nightly` versions so you can choose whichever one fits you best. The minimum required Rust version is 1.63.
|
||||
First, make sure you have Rust installed on your system. If you haven't already done so, try following the instructions [here](https://www.rust-lang.org/tools/install). PyO3 runs on both the `stable` and `nightly` versions so you can choose whichever one fits you best. The minimum required Rust version is 1.56.
|
||||
|
||||
If you can run `rustc --version` and the version is new enough you're good to go!
|
||||
|
||||
|
@ -18,14 +18,19 @@ To use PyO3, you need at least Python 3.7. While you can simply use the default
|
|||
|
||||
While you can use any virtualenv manager you like, we recommend the use of `pyenv` in particular if you want to develop or test for multiple different Python versions, so that is what the examples in this book will use. The installation instructions for `pyenv` can be found [here](https://github.com/pyenv/pyenv#getting-pyenv). (Note: To get the `pyenv activate` and `pyenv virtualenv` commands, you will also need to install the [`pyenv-virtualenv`](https://github.com/pyenv/pyenv-virtualenv) plugin. The [pyenv installer](https://github.com/pyenv/pyenv-installer#installation--update--uninstallation) will install both together.)
|
||||
|
||||
It can be useful to keep the sources used when installing using `pyenv` so that future debugging can see the original source files. This can be done by passing the `--keep` flag as part of the `pyenv install` command.
|
||||
If you intend to run Python from Rust (for example in unit tests) you should set the following environment variable when installing a new Python version using `pyenv`:
|
||||
```bash
|
||||
PYTHON_CONFIGURE_OPTS="--enable-shared"
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
pyenv install 3.12 --keep
|
||||
env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.12
|
||||
```
|
||||
|
||||
You can read more about `pyenv`'s configuration options [here](https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#building-with---enable-shared).
|
||||
|
||||
### Building
|
||||
|
||||
There are a number of build and Python package management systems such as [`setuptools-rust`](https://github.com/PyO3/setuptools-rust) or [manually](./building-and-distribution.md#manual-builds). We recommend the use of `maturin`, which you can install [here](https://maturin.rs/installation.html). It is developed to work with PyO3 and provides the most "batteries included" experience, especially if you are aiming to publish to PyPI. `maturin` is just a Python package, so you can add it in the same way you already install Python packages.
|
||||
|
@ -159,7 +164,8 @@ fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
|
|||
/// import the module.
|
||||
#[pymodule]
|
||||
fn pyo3_example(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(sum_as_string, m)?)
|
||||
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -175,7 +181,3 @@ $ python
|
|||
```
|
||||
|
||||
For more instructions on how to use Python code from Rust, see the [Python from Rust](python-from-rust.md) page.
|
||||
|
||||
## Maturin Import Hook
|
||||
|
||||
In development, any changes in the code would require running `maturin develop` before testing. To streamline the development process, you may want to install [Maturin Import Hook](https://github.com/PyO3/maturin-import-hook) which will run `maturin develop` automatically when the library with code changes is being imported.
|
||||
|
|
|
@ -34,11 +34,9 @@ held. (If PyO3 could not assume this, every PyO3 API would need to take a
|
|||
very simple and easy-to-understand programs like this:
|
||||
|
||||
```rust
|
||||
# #![allow(unused_imports)]
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
Python::with_gil(|py| -> PyResult<()> {
|
||||
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
|
||||
let hello = py
|
||||
|
@ -59,11 +57,9 @@ it owns are decreased, releasing them to the Python garbage collector. Most
|
|||
of the time we don't have to think about this, but consider the following:
|
||||
|
||||
```rust
|
||||
# #![allow(unused_imports)]
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
Python::with_gil(|py| -> PyResult<()> {
|
||||
for _ in 0..10 {
|
||||
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
|
||||
|
@ -100,11 +96,9 @@ In general we don't want unbounded memory growth during loops! One workaround
|
|||
is to acquire and release the GIL with each iteration of the loop.
|
||||
|
||||
```rust
|
||||
# #![allow(unused_imports)]
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
for _ in 0..10 {
|
||||
Python::with_gil(|py| -> PyResult<()> {
|
||||
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
|
||||
|
@ -124,11 +118,9 @@ times. Another workaround is to work with the `GILPool` object directly, but
|
|||
this is unsafe.
|
||||
|
||||
```rust
|
||||
# #![allow(unused_imports)]
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
Python::with_gil(|py| -> PyResult<()> {
|
||||
for _ in 0..10 {
|
||||
#[allow(deprecated)] // `new_pool` is not needed in code not using the GIL Refs API
|
||||
|
@ -154,7 +146,8 @@ at the end of each loop iteration, before the `with_gil()` closure ends.
|
|||
|
||||
When doing this, you must be very careful to ensure that once the `GILPool` is
|
||||
dropped you do not retain access to any owned references created after the
|
||||
`GILPool` was created. Read the documentation for `Python::new_pool()`
|
||||
`GILPool` was created. Read the
|
||||
[documentation for `Python::new_pool()`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.new_pool)
|
||||
for more information on safety.
|
||||
|
||||
This memory management can also be applicable when writing extension modules.
|
||||
|
@ -184,11 +177,9 @@ What happens to the memory when the last `Py<PyAny>` is dropped and its
|
|||
reference count reaches zero? It depends whether or not we are holding the GIL.
|
||||
|
||||
```rust
|
||||
# #![allow(unused_imports)]
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
Python::with_gil(|py| -> PyResult<()> {
|
||||
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
|
||||
let hello: Py<PyString> = py.eval("\"Hello World!\"", None, None)?.extract()?;
|
||||
|
@ -212,13 +203,9 @@ This example wasn't very interesting. We could have just used a GIL-bound
|
|||
we are *not* holding the GIL?
|
||||
|
||||
```rust
|
||||
# #![allow(unused_imports, dead_code)]
|
||||
# #[cfg(not(pyo3_disable_reference_pool))] {
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
# {
|
||||
let hello: Py<PyString> = Python::with_gil(|py| {
|
||||
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
|
||||
py.eval("\"Hello World!\"", None, None)?.extract()
|
||||
|
@ -237,28 +224,22 @@ Python::with_gil(|py|
|
|||
// Memory for `hello` is released here.
|
||||
# ()
|
||||
);
|
||||
# }
|
||||
# Ok(())
|
||||
# }
|
||||
# }
|
||||
```
|
||||
|
||||
When `hello` is dropped *nothing* happens to the pointed-to memory on Python's
|
||||
heap because nothing _can_ happen if we're not holding the GIL. Fortunately,
|
||||
the memory isn't leaked. If the `pyo3_disable_reference_pool` conditional compilation flag
|
||||
is not enabled, PyO3 keeps track of the memory internally and will release it
|
||||
the next time we acquire the GIL.
|
||||
the memory isn't leaked. PyO3 keeps track of the memory internally and will
|
||||
release it the next time we acquire the GIL.
|
||||
|
||||
We can avoid the delay in releasing memory if we are careful to drop the
|
||||
`Py<Any>` while the GIL is held.
|
||||
|
||||
```rust
|
||||
# #![allow(unused_imports)]
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
# {
|
||||
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
|
||||
let hello: Py<PyString> =
|
||||
Python::with_gil(|py| py.eval("\"Hello World!\"", None, None)?.extract())?;
|
||||
|
@ -271,7 +252,6 @@ Python::with_gil(|py| {
|
|||
}
|
||||
drop(hello); // Memory released here.
|
||||
});
|
||||
# }
|
||||
# Ok(())
|
||||
# }
|
||||
```
|
||||
|
@ -283,12 +263,9 @@ that rather than being released immediately, the memory will not be released
|
|||
until the GIL is dropped.
|
||||
|
||||
```rust
|
||||
# #![allow(unused_imports)]
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
# {
|
||||
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
|
||||
let hello: Py<PyString> =
|
||||
Python::with_gil(|py| py.eval("\"Hello World!\"", None, None)?.extract())?;
|
||||
|
@ -303,7 +280,6 @@ Python::with_gil(|py| {
|
|||
// Do more stuff...
|
||||
// Memory released here at end of `with_gil()` closure.
|
||||
});
|
||||
# }
|
||||
# Ok(())
|
||||
# }
|
||||
```
|
||||
|
|
|
@ -3,153 +3,8 @@
|
|||
This guide can help you upgrade code through breaking changes from one PyO3 version to the next.
|
||||
For a detailed list of all changes, see the [CHANGELOG](changelog.md).
|
||||
|
||||
## from 0.21.* to 0.22
|
||||
|
||||
### Deprecation of `gil-refs` feature continues
|
||||
<details open>
|
||||
<summary><small>Click to expand</small></summary>
|
||||
|
||||
Following the introduction of the "Bound" API in PyO3 0.21 and the planned removal of the "GIL Refs" API, all functionality related to GIL Refs is now gated behind the `gil-refs` feature and emits a deprecation warning on use.
|
||||
|
||||
See <a href="#from-021-to-022">the 0.21 migration entry</a> for help upgrading.
|
||||
</details>
|
||||
|
||||
### Deprecation of implicit default for trailing optional arguments
|
||||
<details open>
|
||||
<summary><small>Click to expand</small></summary>
|
||||
|
||||
With `pyo3` 0.22 the implicit `None` default for trailing `Option<T>` type argument is deprecated. To migrate, place a `#[pyo3(signature = (...))]` attribute on affected functions or methods and specify the desired behavior.
|
||||
The migration warning specifies the corresponding signature to keep the current behavior. With 0.23 the signature will be required for any function containing `Option<T>` type parameters to prevent accidental
|
||||
and unnoticed changes in behavior. With 0.24 this restriction will be lifted again and `Option<T>` type arguments will be treated as any other argument _without_ special handling.
|
||||
|
||||
Before:
|
||||
|
||||
```rust
|
||||
# #![allow(deprecated, dead_code)]
|
||||
# use pyo3::prelude::*;
|
||||
#[pyfunction]
|
||||
fn increment(x: u64, amount: Option<u64>) -> u64 {
|
||||
x + amount.unwrap_or(1)
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# use pyo3::prelude::*;
|
||||
#[pyfunction]
|
||||
#[pyo3(signature = (x, amount=None))]
|
||||
fn increment(x: u64, amount: Option<u64>) -> u64 {
|
||||
x + amount.unwrap_or(1)
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### `Py::clone` is now gated behind the `py-clone` feature
|
||||
<details open>
|
||||
<summary><small>Click to expand</small></summary>
|
||||
If you rely on `impl<T> Clone for Py<T>` to fulfil trait requirements imposed by existing Rust code written without PyO3-based code in mind, the newly introduced feature `py-clone` must be enabled.
|
||||
|
||||
However, take care to note that the behaviour is different from previous versions. If `Clone` was called without the GIL being held, we tried to delay the application of these reference count increments until PyO3-based code would re-acquire it. This turned out to be impossible to implement in a sound manner and hence was removed. Now, if `Clone` is called without the GIL being held, we panic instead for which calling code might not be prepared.
|
||||
|
||||
Related to this, we also added a `pyo3_disable_reference_pool` conditional compilation flag which removes the infrastructure necessary to apply delayed reference count decrements implied by `impl<T> Drop for Py<T>`. They do not appear to be a soundness hazard as they should lead to memory leaks in the worst case. However, the global synchronization adds significant overhead to cross the Python-Rust boundary. Enabling this feature will remove these costs and make the `Drop` implementation abort the process if called without the GIL being held instead.
|
||||
</details>
|
||||
|
||||
### Require explicit opt-in for comparison for simple enums
|
||||
<details open>
|
||||
<summary><small>Click to expand</small></summary>
|
||||
|
||||
With `pyo3` 0.22 the new `#[pyo3(eq)]` options allows automatic implementation of Python equality using Rust's `PartialEq`. Previously simple enums automatically implemented equality in terms of their discriminants. To make PyO3 more consistent, this automatic equality implementation is deprecated in favour of having opt-ins for all `#[pyclass]` types. Similarly, simple enums supported comparison with integers, which is not covered by Rust's `PartialEq` derive, so has been split out into the `#[pyo3(eq_int)]` attribute.
|
||||
|
||||
To migrate, place a `#[pyo3(eq, eq_int)]` attribute on simple enum classes.
|
||||
|
||||
Before:
|
||||
|
||||
```rust
|
||||
# #![allow(deprecated, dead_code)]
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
enum SimpleEnum {
|
||||
VariantA,
|
||||
VariantB = 42,
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
enum SimpleEnum {
|
||||
VariantA,
|
||||
VariantB = 42,
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### `PyType::name` reworked to better match Python `__name__`
|
||||
<details open>
|
||||
<summary><small>Click to expand</small></summary>
|
||||
|
||||
This function previously would try to read directly from Python type objects' C API field (`tp_name`), in which case it
|
||||
would return a `Cow::Borrowed`. However the contents of `tp_name` don't have well-defined semantics.
|
||||
|
||||
Instead `PyType::name()` now returns the equivalent of Python `__name__` and returns `PyResult<Bound<'py, PyString>>`.
|
||||
|
||||
The closest equivalent to PyO3 0.21's version of `PyType::name()` has been introduced as a new function `PyType::fully_qualified_name()`,
|
||||
which is equivalent to `__module__` and `__qualname__` joined as `module.qualname`.
|
||||
|
||||
Before:
|
||||
|
||||
```rust,ignore
|
||||
# #![allow(deprecated, dead_code)]
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::{PyBool};
|
||||
# fn main() -> PyResult<()> {
|
||||
Python::with_gil(|py| {
|
||||
let bool_type = py.get_type_bound::<PyBool>();
|
||||
let name = bool_type.name()?.into_owned();
|
||||
println!("Hello, {}", name);
|
||||
|
||||
let mut name_upper = bool_type.name()?;
|
||||
name_upper.to_mut().make_ascii_uppercase();
|
||||
println!("Hello, {}", name_upper);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
# }
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::{PyBool};
|
||||
# fn main() -> PyResult<()> {
|
||||
Python::with_gil(|py| {
|
||||
let bool_type = py.get_type_bound::<PyBool>();
|
||||
let name = bool_type.name()?;
|
||||
println!("Hello, {}", name);
|
||||
|
||||
// (if the full dotted path was desired, switch from `name()` to `fully_qualified_name()`)
|
||||
let mut name_upper = bool_type.fully_qualified_name()?.to_string();
|
||||
name_upper.make_ascii_uppercase();
|
||||
println!("Hello, {}", name_upper);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
# }
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
## from 0.20.* to 0.21
|
||||
<details>
|
||||
<details open>
|
||||
<summary><small>Click to expand</small></summary>
|
||||
|
||||
PyO3 0.21 introduces a new `Bound<'py, T>` smart pointer which replaces the existing "GIL Refs" API to interact with Python objects. For example, in PyO3 0.20 the reference `&'py PyAny` would be used to interact with Python objects. In PyO3 0.21 the updated type is `Bound<'py, PyAny>`. Making this change moves Rust ownership semantics out of PyO3's internals and into user code. This change fixes [a known soundness edge case of interaction with gevent](https://github.com/PyO3/pyo3/issues/3668) as well as improves CPU and [memory performance](https://github.com/PyO3/pyo3/issues/1056). For a full history of discussion see https://github.com/PyO3/pyo3/issues/3382.
|
||||
|
@ -167,7 +22,7 @@ The following sections are laid out in this order.
|
|||
</details>
|
||||
|
||||
### Enable the `gil-refs` feature
|
||||
<details>
|
||||
<details open>
|
||||
<summary><small>Click to expand</small></summary>
|
||||
|
||||
To make the transition for the PyO3 ecosystem away from the GIL Refs API as smooth as possible, in PyO3 0.21 no APIs consuming or producing GIL Refs have been altered. Instead, variants using `Bound<T>` smart pointers have been introduced, for example `PyTuple::new_bound` which returns `Bound<PyTuple>` is the replacement form of `PyTuple::new`. The GIL Ref APIs have been deprecated, but to make migration easier it is possible to disable these deprecation warnings by enabling the `gil-refs` feature.
|
||||
|
@ -194,18 +49,18 @@ pyo3 = { version = "0.21", features = ["gil-refs"] }
|
|||
</details>
|
||||
|
||||
### `PyTypeInfo` and `PyTryFrom` have been adjusted
|
||||
<details>
|
||||
<details open>
|
||||
<summary><small>Click to expand</small></summary>
|
||||
|
||||
The `PyTryFrom` trait has aged poorly, its `try_from` method now conflicts with `TryFrom::try_from` in the 2021 edition prelude. A lot of its functionality was also duplicated with `PyTypeInfo`.
|
||||
|
||||
To tighten up the PyO3 traits as part of the deprecation of the GIL Refs API the `PyTypeInfo` trait has had a simpler companion `PyTypeCheck`. The methods `PyAny::downcast` and `PyAny::downcast_exact` no longer use `PyTryFrom` as a bound, instead using `PyTypeCheck` and `PyTypeInfo` respectively.
|
||||
To tighten up the PyO3 traits as part of the deprecation of the GIL Refs API the `PyTypeInfo` trait has had a simpler companion `PyTypeCheck`. The methods [`PyAny::downcast`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html#method.downcast) and [`PyAny::downcast_exact`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html#method.downcast_exact) no longer use `PyTryFrom` as a bound, instead using `PyTypeCheck` and `PyTypeInfo` respectively.
|
||||
|
||||
To migrate, switch all type casts to use `obj.downcast()` instead of `try_from(obj)` (and similar for `downcast_exact`).
|
||||
|
||||
Before:
|
||||
|
||||
```rust,ignore
|
||||
```rust
|
||||
# #![allow(deprecated)]
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::{PyInt, PyList};
|
||||
|
@ -220,7 +75,7 @@ Python::with_gil(|py| {
|
|||
|
||||
After:
|
||||
|
||||
```rust,ignore
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::{PyInt, PyList};
|
||||
# fn main() -> PyResult<()> {
|
||||
|
@ -237,14 +92,15 @@ Python::with_gil(|py| {
|
|||
</details>
|
||||
|
||||
### `Iter(A)NextOutput` are deprecated
|
||||
<details>
|
||||
<details open>
|
||||
<summary><small>Click to expand</small></summary>
|
||||
|
||||
The `__next__` and `__anext__` magic methods can now return any type convertible into Python objects directly just like all other `#[pymethods]`. The `IterNextOutput` used by `__next__` and `IterANextOutput` used by `__anext__` are subsequently deprecated. Most importantly, this change allows returning an awaitable from `__anext__` without non-sensically wrapping it into `Yield` or `Some`. Only the return types `Option<T>` and `Result<Option<T>, E>` are still handled in a special manner where `Some(val)` yields `val` and `None` stops iteration.
|
||||
|
||||
Starting with an implementation of a Python iterator using `IterNextOutput`, e.g.
|
||||
|
||||
```rust,ignore
|
||||
```rust
|
||||
#![allow(deprecated)]
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::iter::IterNextOutput;
|
||||
|
||||
|
@ -358,21 +214,21 @@ impl PyClassAsyncIter {
|
|||
</details>
|
||||
|
||||
### `PyType::name` has been renamed to `PyType::qualname`
|
||||
<details>
|
||||
<details open>
|
||||
<summary><small>Click to expand</small></summary>
|
||||
|
||||
`PyType::name` has been renamed to `PyType::qualname` to indicate that it does indeed return the [qualified name](https://docs.python.org/3/glossary.html#term-qualified-name), matching the `__qualname__` attribute. The newly added `PyType::name` yields the full name including the module name now which corresponds to `__module__.__name__` on the level of attributes.
|
||||
</details>
|
||||
|
||||
### `PyCell` has been deprecated
|
||||
<details>
|
||||
<details open>
|
||||
<summary><small>Click to expand</small></summary>
|
||||
|
||||
Interactions with Python objects implemented in Rust no longer need to go though `PyCell<T>`. Instead iteractions with Python object now consistently go through `Bound<T>` or `Py<T>` independently of whether `T` is native Python object or a `#[pyclass]` implemented in Rust. Use `Bound::new` or `Py::new` respectively to create and `Bound::borrow(_mut)` / `Py::borrow(_mut)` to borrow the Rust object.
|
||||
</details>
|
||||
|
||||
### Migrating from the GIL Refs API to `Bound<T>`
|
||||
<details>
|
||||
<details open>
|
||||
<summary><small>Click to expand</small></summary>
|
||||
|
||||
To minimise breakage of code using the GIL Refs API, the `Bound<T>` smart pointer has been introduced by adding complements to all functions which accept or return GIL Refs. This allows code to migrate by replacing the deprecated APIs with the new ones.
|
||||
|
@ -470,7 +326,7 @@ Despite a large amount of deprecations warnings produced by PyO3 to aid with the
|
|||
</details>
|
||||
|
||||
### Deactivating the `gil-refs` feature
|
||||
<details>
|
||||
<details open>
|
||||
<summary><small>Click to expand</small></summary>
|
||||
|
||||
As a final step of migration, deactivating the `gil-refs` feature will set up code for best performance and is intended to set up a forward-compatible API for PyO3 0.22.
|
||||
|
@ -483,12 +339,12 @@ To make PyO3's core functionality continue to work while the GIL Refs API is in
|
|||
|
||||
PyO3 0.21 has introduced the [`PyBackedStr`]({{#PYO3_DOCS_URL}}/pyo3/pybacked/struct.PyBackedStr.html) and [`PyBackedBytes`]({{#PYO3_DOCS_URL}}/pyo3/pybacked/struct.PyBackedBytes.html) types to help with this case. The easiest way to avoid lifetime challenges from extracting `&str` is to use these. For more complex types like `Vec<&str>`, is now impossible to extract directly from a Python object and `Vec<PyBackedStr>` is the recommended upgrade path.
|
||||
|
||||
A key thing to note here is because extracting to these types now ties them to the input lifetime, some extremely common patterns may need to be split into multiple Rust lines. For example, the following snippet of calling `.extract::<&str>()` directly on the result of `.getattr()` needs to be adjusted when deactivating the `gil-refs` feature.
|
||||
A key thing to note here is because extracting to these types now ties them to the input lifetime, some extremely common patterns may need to be split into multiple Rust lines. For example, the following snippet of calling `.extract::<&str>()` directly on the result of `.getattr()` needs to be adjusted when deactivating the `gil-refs-migration` feature.
|
||||
|
||||
Before:
|
||||
|
||||
```rust
|
||||
# #[cfg(feature = "gil-refs")] {
|
||||
# #[cfg(feature = "gil-refs-migration")] {
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::{PyList, PyType};
|
||||
# fn example<'py>(py: Python<'py>) -> PyResult<()> {
|
||||
|
@ -537,7 +393,6 @@ assert_eq!(&*name, "list");
|
|||
# }
|
||||
# Python::with_gil(example).unwrap();
|
||||
```
|
||||
</details>
|
||||
|
||||
## from 0.19.* to 0.20
|
||||
|
||||
|
@ -786,7 +641,7 @@ drop(second);
|
|||
|
||||
The replacement is [`Python::with_gil`](https://docs.rs/pyo3/0.18.3/pyo3/marker/struct.Python.html#method.with_gil) which is more cumbersome but enforces the proper nesting by design, e.g.
|
||||
|
||||
```rust,ignore
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# use pyo3::prelude::*;
|
||||
|
||||
|
@ -811,7 +666,7 @@ let second = Python::with_gil(|py| Object::new(py));
|
|||
drop(first);
|
||||
drop(second);
|
||||
|
||||
// Or it ensures releasing the inner lock before the outer one.
|
||||
// Or it ensure releasing the inner lock before the outer one.
|
||||
Python::with_gil(|py| {
|
||||
let first = Object::new(py);
|
||||
let second = Python::with_gil(|py| Object::new(py));
|
||||
|
@ -1234,7 +1089,7 @@ An additional advantage of using Rust's indexing conventions for these types is
|
|||
that these types can now also support Rust's indexing operators as part of a
|
||||
consistent API:
|
||||
|
||||
```rust,ignore
|
||||
```rust
|
||||
#![allow(deprecated)]
|
||||
use pyo3::{Python, types::PyList};
|
||||
|
||||
|
@ -1710,7 +1565,7 @@ For more, see [the constructor section](class.md#constructor) of this guide.
|
|||
<details>
|
||||
<summary><small>Click to expand</small></summary>
|
||||
|
||||
PyO3 0.9 introduces `PyCell`, which is a [`RefCell`]-like object wrapper
|
||||
PyO3 0.9 introduces [`PyCell`], which is a [`RefCell`]-like object wrapper
|
||||
for ensuring Rust's rules regarding aliasing of references are upheld.
|
||||
For more detail, see the
|
||||
[Rust Book's section on Rust's rules of references](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#the-rules-of-references)
|
||||
|
@ -1759,7 +1614,7 @@ However, for `#[pyproto]` and some functions, you need to manually fix the code.
|
|||
In 0.8 object creation was done with `PyRef::new` and `PyRefMut::new`.
|
||||
In 0.9 these have both been removed.
|
||||
To upgrade code, please use
|
||||
`PyCell::new` instead.
|
||||
[`PyCell::new`]({{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyCell.html#method.new) instead.
|
||||
If you need [`PyRef`] or [`PyRefMut`], just call `.borrow()` or `.borrow_mut()`
|
||||
on the newly-created `PyCell`.
|
||||
|
||||
|
@ -1889,6 +1744,7 @@ impl PySequenceProtocol for ByteSequence {
|
|||
|
||||
[`FromPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.FromPyObject.html
|
||||
[`PyAny`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html
|
||||
[`PyCell`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyCell.html
|
||||
[`PyBorrowMutError`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyBorrowMutError.html
|
||||
[`PyRef`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html
|
||||
[`PyRefMut`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html
|
||||
|
|
|
@ -13,7 +13,8 @@ fn double(x: usize) -> usize {
|
|||
/// This module is implemented in Rust.
|
||||
#[pymodule]
|
||||
fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(double, m)?)
|
||||
m.add_function(wrap_pyfunction!(double, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -31,9 +32,11 @@ fn double(x: usize) -> usize {
|
|||
x * 2
|
||||
}
|
||||
|
||||
#[pymodule(name = "custom_name")]
|
||||
#[pymodule]
|
||||
#[pyo3(name = "custom_name")]
|
||||
fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(double, m)?)
|
||||
m.add_function(wrap_pyfunction!(double, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -77,7 +80,8 @@ fn parent_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|||
fn register_child_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
let child_module = PyModule::new_bound(parent_module.py(), "child_module")?;
|
||||
child_module.add_function(wrap_pyfunction!(func, &child_module)?)?;
|
||||
parent_module.add_submodule(&child_module)
|
||||
parent_module.add_submodule(&child_module)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
|
@ -102,12 +106,14 @@ submodules by using `from parent_module import child_module`. For more informati
|
|||
|
||||
It is not necessary to add `#[pymodule]` on nested modules, which is only required on the top-level module.
|
||||
|
||||
## Declarative modules
|
||||
## Declarative modules (experimental)
|
||||
|
||||
Another syntax based on Rust inline modules is also available to declare modules.
|
||||
The `experimental-declarative-modules` feature must be enabled to use it.
|
||||
|
||||
For example:
|
||||
```rust
|
||||
# #[cfg(feature = "experimental-declarative-modules")]
|
||||
# mod declarative_module_test {
|
||||
use pyo3::prelude::*;
|
||||
|
||||
|
@ -139,42 +145,15 @@ mod my_extension {
|
|||
#[pymodule_init]
|
||||
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
// Arbitrary code to run at the module initialization
|
||||
m.add("double2", m.getattr("double")?)
|
||||
m.add("double2", m.getattr("double")?)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
# }
|
||||
```
|
||||
|
||||
The `#[pymodule]` macro automatically sets the `module` attribute of the `#[pyclass]` macros declared inside of it with its name.
|
||||
For nested modules, the name of the parent module is automatically added.
|
||||
In the following example, the `Unit` class will have for `module` `my_extension.submodule` because it is properly nested
|
||||
but the `Ext` class will have for `module` the default `builtins` because it not nested.
|
||||
|
||||
```rust
|
||||
# mod declarative_module_module_attr_test {
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass]
|
||||
struct Ext;
|
||||
|
||||
#[pymodule]
|
||||
mod my_extension {
|
||||
use super::*;
|
||||
|
||||
#[pymodule_export]
|
||||
use super::Ext;
|
||||
|
||||
#[pymodule]
|
||||
mod submodule {
|
||||
use super::*;
|
||||
// This is a submodule
|
||||
|
||||
#[pyclass] // This will be part of the module
|
||||
struct Unit;
|
||||
}
|
||||
}
|
||||
# }
|
||||
```
|
||||
It is possible to customize the `module` value for a `#[pymodule]` with the `#[pyo3(module = "MY_MODULE")]` option.
|
||||
|
||||
You can provide the `submodule` argument to `pymodule()` for modules that are not top-level modules -- it is automatically set for modules nested inside of a `#[pymodule]`.
|
||||
Some changes are planned to this feature before stabilization, like automatically
|
||||
filling submodules into `sys.modules` to allow easier imports (see [issue #759](https://github.com/PyO3/pyo3/issues/759))
|
||||
and filling the `module` argument of inlined `#[pyclass]` automatically with the proper module name.
|
||||
Macro names might also change.
|
||||
See [issue #3900](https://github.com/PyO3/pyo3/issues/3900) to track this feature progress.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Parallelism
|
||||
|
||||
CPython has the infamous [Global Interpreter Lock](https://docs.python.org/3/glossary.html#term-global-interpreter-lock), which prevents several threads from executing Python bytecode in parallel. This makes threading in Python a bad fit for [CPU-bound](https://en.wikipedia.org/wiki/CPU-bound) tasks and often forces developers to accept the overhead of multiprocessing.
|
||||
CPython has the infamous [Global Interpreter Lock](https://docs.python.org/3/glossary.html#term-global-interpreter-lock), which prevents several threads from executing Python bytecode in parallel. This makes threading in Python a bad fit for [CPU-bound](https://stackoverflow.com/questions/868568/) tasks and often forces developers to accept the overhead of multiprocessing.
|
||||
|
||||
In PyO3 parallelism can be easily achieved in Rust-only code. Let's take a look at our [word-count](https://github.com/PyO3/pyo3/blob/main/examples/word-count/src/lib.rs) example, where we have a `search` function that utilizes the [rayon](https://github.com/rayon-rs/rayon) crate to count words in parallel.
|
||||
```rust,no_run
|
||||
|
|
|
@ -96,47 +96,3 @@ impl PartialEq<Foo> for FooBound<'_> {
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Disable the global reference pool
|
||||
|
||||
PyO3 uses global mutable state to keep track of deferred reference count updates implied by `impl<T> Drop for Py<T>` being called without the GIL being held. The necessary synchronization to obtain and apply these reference count updates when PyO3-based code next acquires the GIL is somewhat expensive and can become a significant part of the cost of crossing the Python-Rust boundary.
|
||||
|
||||
This functionality can be avoided by setting the `pyo3_disable_reference_pool` conditional compilation flag. This removes the global reference pool and the associated costs completely. However, it does _not_ remove the `Drop` implementation for `Py<T>` which is necessary to interoperate with existing Rust code written without PyO3-based code in mind. To stay compatible with the wider Rust ecosystem in these cases, we keep the implementation but abort when `Drop` is called without the GIL being held. If `pyo3_leak_on_drop_without_reference_pool` is additionally enabled, objects dropped without the GIL being held will be leaked instead which is always sound but might have determinal effects like resource exhaustion in the long term.
|
||||
|
||||
This limitation is important to keep in mind when this setting is used, especially when embedding Python code into a Rust application as it is quite easy to accidentally drop a `Py<T>` (or types containing it like `PyErr`, `PyBackedStr` or `PyBackedBytes`) returned from `Python::with_gil` without making sure to re-acquire the GIL beforehand. For example, the following code
|
||||
|
||||
```rust,ignore
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyList;
|
||||
let numbers: Py<PyList> = Python::with_gil(|py| PyList::empty_bound(py).unbind());
|
||||
|
||||
Python::with_gil(|py| {
|
||||
numbers.bind(py).append(23).unwrap();
|
||||
});
|
||||
|
||||
Python::with_gil(|py| {
|
||||
numbers.bind(py).append(42).unwrap();
|
||||
});
|
||||
```
|
||||
|
||||
will abort if the list not explicitly disposed via
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyList;
|
||||
let numbers: Py<PyList> = Python::with_gil(|py| PyList::empty_bound(py).unbind());
|
||||
|
||||
Python::with_gil(|py| {
|
||||
numbers.bind(py).append(23).unwrap();
|
||||
});
|
||||
|
||||
Python::with_gil(|py| {
|
||||
numbers.bind(py).append(42).unwrap();
|
||||
});
|
||||
|
||||
Python::with_gil(move |py| {
|
||||
drop(numbers);
|
||||
});
|
||||
```
|
||||
|
||||
[conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
If you already have some existing Python code that you need to execute from Rust, the following FAQs can help you select the right PyO3 functionality for your situation:
|
||||
|
||||
## Want to access Python APIs? Then use `PyModule::import_bound`.
|
||||
## Want to access Python APIs? Then use `PyModule::import`.
|
||||
|
||||
[`PyModule::import_bound`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.import_bound) can
|
||||
[`Pymodule::import`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.import) can
|
||||
be used to get handle to a Python module from Rust. You can use this to import and use any Python
|
||||
module available in your environment.
|
||||
|
||||
|
@ -24,9 +24,9 @@ fn main() -> PyResult<()> {
|
|||
}
|
||||
```
|
||||
|
||||
## Want to run just an expression? Then use `eval_bound`.
|
||||
## Want to run just an expression? Then use `eval`.
|
||||
|
||||
[`Python::eval_bound`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.eval_bound) is
|
||||
[`Python::eval`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.eval) is
|
||||
a method to execute a [Python expression](https://docs.python.org/3.7/reference/expressions.html)
|
||||
and return the evaluated value as a `Bound<'py, PyAny>` object.
|
||||
|
||||
|
@ -47,14 +47,14 @@ Python::with_gil(|py| {
|
|||
# }
|
||||
```
|
||||
|
||||
## Want to run statements? Then use `run_bound`.
|
||||
## Want to run statements? Then use `run`.
|
||||
|
||||
[`Python::run_bound`] is a method to execute one or more
|
||||
[`Python::run`] is a method to execute one or more
|
||||
[Python statements](https://docs.python.org/3.7/reference/simple_stmts.html).
|
||||
This method returns nothing (like any Python statement), but you can get
|
||||
access to manipulated objects via the `locals` dict.
|
||||
|
||||
You can also use the [`py_run!`] macro, which is a shorthand for [`Python::run_bound`].
|
||||
You can also use the [`py_run!`] macro, which is a shorthand for [`Python::run`].
|
||||
Since [`py_run!`] panics on exceptions, we recommend you use this macro only for
|
||||
quickly testing your Python extensions.
|
||||
|
||||
|
@ -95,9 +95,9 @@ assert userdata.as_tuple() == userdata_as_tuple
|
|||
# }
|
||||
```
|
||||
|
||||
## You have a Python file or code snippet? Then use `PyModule::from_code_bound`.
|
||||
## You have a Python file or code snippet? Then use `PyModule::from_code`.
|
||||
|
||||
[`PyModule::from_code_bound`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.from_code_bound)
|
||||
[`PyModule::from_code`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.from_code)
|
||||
can be used to generate a Python module which can then be used just as if it was imported with
|
||||
`PyModule::import`.
|
||||
|
||||
|
@ -171,7 +171,7 @@ fn main() -> PyResult<()> {
|
|||
```
|
||||
|
||||
If `append_to_inittab` cannot be used due to constraints in the program,
|
||||
an alternative is to create a module using [`PyModule::new_bound`]
|
||||
an alternative is to create a module using [`PyModule::new`]
|
||||
and insert it manually into `sys.modules`:
|
||||
|
||||
```rust
|
||||
|
@ -394,4 +394,4 @@ Python::with_gil(|py| -> PyResult<()> {
|
|||
```
|
||||
|
||||
|
||||
[`PyModule::new_bound`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.new_bound
|
||||
[`PyModule::new`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.new
|
||||
|
|
|
@ -107,7 +107,7 @@ fn main() -> PyResult<()> {
|
|||
|
||||
<div class="warning">
|
||||
|
||||
During PyO3's [migration from "GIL Refs" to the `Bound<T>` smart pointer](../migration.md#migrating-from-the-gil-refs-api-to-boundt), `Py<T>::call` is temporarily named [`Py<T>::call_bound`]({{#PYO3_DOCS_URL}}/pyo3/struct.Py.html#method.call_bound) (and `call_method` is temporarily `call_method_bound`).
|
||||
During PyO3's [migration from "GIL Refs" to the `Bound<T>` smart pointer](../migration.md#migrating-from-the-gil-refs-api-to-boundt), [`Py<T>::call`]({{#PYO3_DOCS_URL}}/pyo3/struct.Py.html#method.call) is temporarily named `call_bound` (and `call_method` is temporarily `call_method_bound`).
|
||||
|
||||
(This temporary naming is only the case for the `Py<T>` smart pointer. The methods on the `&PyAny` GIL Ref such as `call` have not been given replacements, and the methods on the `Bound<PyAny>` smart pointer such as [`Bound<PyAny>::call`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call) already use follow the newest API conventions.)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Using in Python a Rust function with trait bounds
|
||||
|
||||
PyO3 allows for easy conversion from Rust to Python for certain functions and classes (see the [conversion table](conversions/tables.md)).
|
||||
PyO3 allows for easy conversion from Rust to Python for certain functions and classes (see the [conversion table](conversions/tables.md).
|
||||
However, it is not always straightforward to convert Rust code that requires a given trait implementation as an argument.
|
||||
|
||||
This tutorial explains how to convert a Rust function that takes a trait as argument for use in Python with classes implementing the same methods as the trait.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
PyO3 offers two main sets of types to interact with Python objects. This section of the guide expands into detail about these types and how to choose which to use.
|
||||
|
||||
The first set of types are the [smart pointers][smart-pointers] which all Python objects are wrapped in. These are `Py<T>`, `Bound<'py, T>`, and `Borrowed<'a, 'py, T>`. The [first section below](#pyo3s-smart-pointers) expands on each of these in detail and why there are three of them.
|
||||
The first set of types is are the [smart pointers][smart-pointers] which all Python objects are wrapped in. These are `Py<T>`, `Bound<'py, T>`, and `Borrowed<'a, 'py, T>`. The [first section below](#pyo3s-smart-pointers) expands on each of these in detail and why there are three of them.
|
||||
|
||||
The second set of types are types which fill in the generic parameter `T` of the smart pointers. The most common is `PyAny`, which represents any Python object (similar to Python's `typing.Any`). There are also concrete types for many Python built-in types, such as `PyList`, `PyDict`, and `PyTuple`. User defined `#[pyclass]` types also fit this category. The [second section below](#concrete-python-types) expands on how to use these types.
|
||||
|
||||
|
@ -330,10 +330,8 @@ For a `&PyAny` object reference `any` where the underlying object is a Python-na
|
|||
a list:
|
||||
|
||||
```rust
|
||||
# #![allow(unused_imports)]
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyList;
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
# Python::with_gil(|py| -> PyResult<()> {
|
||||
#[allow(deprecated)] // PyList::empty is part of the deprecated "GIL Refs" API.
|
||||
let obj: &PyAny = PyList::empty(py);
|
||||
|
@ -353,10 +351,8 @@ let _: Py<PyList> = obj.extract()?;
|
|||
For a `&PyAny` object reference `any` where the underlying object is a `#[pyclass]`:
|
||||
|
||||
```rust
|
||||
# #![allow(unused_imports)]
|
||||
# use pyo3::prelude::*;
|
||||
# #[pyclass] #[derive(Clone)] struct MyClass { }
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
# Python::with_gil(|py| -> PyResult<()> {
|
||||
#[allow(deprecated)] // into_ref is part of the deprecated GIL Refs API
|
||||
let obj: &PyAny = Py::new(py, MyClass {})?.into_ref(py);
|
||||
|
@ -394,10 +390,8 @@ To see all Python types exposed by `PyO3` consult the [`pyo3::types`][pyo3::type
|
|||
**Conversions:**
|
||||
|
||||
```rust
|
||||
# #![allow(unused_imports)]
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyList;
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
# Python::with_gil(|py| -> PyResult<()> {
|
||||
#[allow(deprecated)] // PyList::empty is part of the deprecated "GIL Refs" API.
|
||||
let list = PyList::empty(py);
|
||||
|
@ -446,10 +440,8 @@ Like PyO3's Python native types, the GIL Ref `&PyCell<T>` implements `Deref<Targ
|
|||
`PyCell<T>` was used to access `&T` and `&mut T` via `PyRef<T>` and `PyRefMut<T>` respectively.
|
||||
|
||||
```rust
|
||||
#![allow(unused_imports)]
|
||||
# use pyo3::prelude::*;
|
||||
# #[pyclass] struct MyClass { }
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
# Python::with_gil(|py| -> PyResult<()> {
|
||||
#[allow(deprecated)] // &PyCell is part of the deprecated GIL Refs API
|
||||
let cell: &PyCell<MyClass> = PyCell::new(py, MyClass {})?;
|
||||
|
@ -469,10 +461,8 @@ let _: &mut MyClass = &mut *py_ref_mut;
|
|||
`PyCell<T>` was also accessed like a Python-native type.
|
||||
|
||||
```rust
|
||||
#![allow(unused_imports)]
|
||||
# use pyo3::prelude::*;
|
||||
# #[pyclass] struct MyClass { }
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
# Python::with_gil(|py| -> PyResult<()> {
|
||||
#[allow(deprecated)] // &PyCell is part of the deprecate GIL Refs API
|
||||
let cell: &PyCell<MyClass> = PyCell::new(py, MyClass {})?;
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Nested declarative `#[pymodule]` are automatically treated as submodules (no `PyInit_` entrypoint is created)
|
|
@ -1 +0,0 @@
|
|||
Deprecate `PyAnyMethods::is_ellipsis` (`Py::is_ellpsis` was deprecated in PyO3 0.20).
|
|
@ -1 +0,0 @@
|
|||
Remove all functionality deprecated in PyO3 0.20.
|
|
@ -1 +0,0 @@
|
|||
Remove all functionality deprecated in PyO3 0.21.
|
|
@ -1,3 +0,0 @@
|
|||
This PR lets PyO3 checks `Py_GIL_DISABLED` build flag and prevents `pyo3-ffi` crate building against GIL-less Python,
|
||||
unless
|
||||
explicitly opt using the `UNSAFE_PYO3_BUILD_FREE_THREADED` environment flag.
|
|
@ -1 +0,0 @@
|
|||
Fix compile failure in declarative `#[pymodule]` under presence of `#![no_implicit_prelude]`.
|
|
@ -1 +0,0 @@
|
|||
`#[pymodule(...)]` now directly accepts all relevant `#[pyo3(...)]` options.
|
80
noxfile.py
80
noxfile.py
|
@ -9,7 +9,7 @@ import tempfile
|
|||
from functools import lru_cache
|
||||
from glob import glob
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple
|
||||
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple
|
||||
|
||||
import nox
|
||||
import nox.command
|
||||
|
@ -30,7 +30,7 @@ PYO3_TARGET = Path(os.environ.get("CARGO_TARGET_DIR", PYO3_DIR / "target")).abso
|
|||
PYO3_GUIDE_SRC = PYO3_DIR / "guide" / "src"
|
||||
PYO3_GUIDE_TARGET = PYO3_TARGET / "guide"
|
||||
PYO3_DOCS_TARGET = PYO3_TARGET / "doc"
|
||||
PY_VERSIONS = ("3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13")
|
||||
PY_VERSIONS = ("3.7", "3.8", "3.9", "3.10", "3.11", "3.12")
|
||||
PYPY_VERSIONS = ("3.7", "3.8", "3.9", "3.10")
|
||||
|
||||
|
||||
|
@ -394,17 +394,8 @@ def check_guide(session: nox.Session):
|
|||
docs(session)
|
||||
session.posargs.extend(posargs)
|
||||
|
||||
if toml is None:
|
||||
session.error("requires Python 3.11 or `toml` to be installed")
|
||||
pyo3_version = toml.loads((PYO3_DIR / "Cargo.toml").read_text())["package"][
|
||||
"version"
|
||||
]
|
||||
|
||||
remaps = {
|
||||
f"file://{PYO3_GUIDE_SRC}/([^/]*/)*?%7B%7B#PYO3_DOCS_URL}}}}": f"file://{PYO3_DOCS_TARGET}",
|
||||
f"https://pyo3.rs/v{pyo3_version}": f"file://{PYO3_GUIDE_TARGET}",
|
||||
"https://pyo3.rs/main/": f"file://{PYO3_GUIDE_TARGET}/",
|
||||
"https://pyo3.rs/latest/": f"file://{PYO3_GUIDE_TARGET}/",
|
||||
"%7B%7B#PYO3_DOCS_VERSION}}": "latest",
|
||||
}
|
||||
remap_args = []
|
||||
|
@ -424,10 +415,10 @@ def check_guide(session: nox.Session):
|
|||
_run(
|
||||
session,
|
||||
"lychee",
|
||||
str(PYO3_DOCS_TARGET),
|
||||
*remap_args,
|
||||
PYO3_DOCS_TARGET,
|
||||
f"--remap=https://pyo3.rs/main/ file://{PYO3_GUIDE_TARGET}/",
|
||||
f"--remap=https://pyo3.rs/latest/ file://{PYO3_GUIDE_TARGET}/",
|
||||
f"--exclude=file://{PYO3_DOCS_TARGET}",
|
||||
"--exclude=http://www.adobe.com/",
|
||||
*session.posargs,
|
||||
)
|
||||
|
||||
|
@ -551,8 +542,8 @@ def check_changelog(session: nox.Session):
|
|||
print(fragment.name)
|
||||
|
||||
|
||||
@nox.session(name="set-msrv-package-versions", venv_backend="none")
|
||||
def set_msrv_package_versions(session: nox.Session):
|
||||
@nox.session(name="set-minimal-package-versions", venv_backend="none")
|
||||
def set_minimal_package_versions(session: nox.Session):
|
||||
from collections import defaultdict
|
||||
|
||||
if toml is None:
|
||||
|
@ -566,11 +557,22 @@ def set_msrv_package_versions(session: nox.Session):
|
|||
"examples/word-count",
|
||||
)
|
||||
min_pkg_versions = {
|
||||
"regex": "1.9.6",
|
||||
"proptest": "1.2.0",
|
||||
"trybuild": "1.0.89",
|
||||
"eyre": "0.6.8",
|
||||
"allocator-api2": "0.2.10",
|
||||
"rust_decimal": "1.26.1",
|
||||
"csv": "1.1.6",
|
||||
"indexmap": "1.6.2",
|
||||
"hashbrown": "0.9.1",
|
||||
"log": "0.4.17",
|
||||
"once_cell": "1.17.2",
|
||||
"rayon": "1.6.1",
|
||||
"rayon-core": "1.10.2",
|
||||
"regex": "1.7.3",
|
||||
"proptest": "1.0.0",
|
||||
"chrono": "0.4.25",
|
||||
"byteorder": "1.4.3",
|
||||
"crossbeam-channel": "0.5.8",
|
||||
"crossbeam-deque": "0.8.3",
|
||||
"crossbeam-epoch": "0.9.15",
|
||||
"crossbeam-utils": "0.8.16",
|
||||
}
|
||||
|
||||
# run cargo update first to ensure that everything is at highest
|
||||
|
@ -639,11 +641,11 @@ def test_version_limits(session: nox.Session):
|
|||
config_file.set("CPython", "3.6")
|
||||
_run_cargo(session, "check", env=env, expect_error=True)
|
||||
|
||||
assert "3.14" not in PY_VERSIONS
|
||||
config_file.set("CPython", "3.14")
|
||||
assert "3.13" not in PY_VERSIONS
|
||||
config_file.set("CPython", "3.13")
|
||||
_run_cargo(session, "check", env=env, expect_error=True)
|
||||
|
||||
# 3.14 CPython should build with forward compatibility
|
||||
# 3.13 CPython should build with forward compatibility
|
||||
env["PYO3_USE_ABI3_FORWARD_COMPATIBILITY"] = "1"
|
||||
_run_cargo(session, "check", env=env)
|
||||
|
||||
|
@ -655,14 +657,6 @@ def test_version_limits(session: nox.Session):
|
|||
config_file.set("PyPy", "3.11")
|
||||
_run_cargo(session, "check", env=env, expect_error=True)
|
||||
|
||||
# Python build with GIL disabled should fail building
|
||||
config_file.set("CPython", "3.13", build_flags=["Py_GIL_DISABLED"])
|
||||
_run_cargo(session, "check", env=env, expect_error=True)
|
||||
|
||||
# Python build with GIL disabled should pass with env flag on
|
||||
env["UNSAFE_PYO3_BUILD_FREE_THREADED"] = "1"
|
||||
_run_cargo(session, "check", env=env)
|
||||
|
||||
|
||||
@nox.session(name="check-feature-powerset", venv_backend="none")
|
||||
def check_feature_powerset(session: nox.Session):
|
||||
|
@ -679,7 +673,7 @@ def check_feature_powerset(session: nox.Session):
|
|||
"default",
|
||||
"auto-initialize",
|
||||
"generate-import-lib",
|
||||
"multiple-pymethods", # Because it's not supported on wasm
|
||||
"multiple-pymethods", # TODO add this after MSRV 1.62
|
||||
}
|
||||
|
||||
features = cargo_toml["features"]
|
||||
|
@ -724,14 +718,10 @@ def check_feature_powerset(session: nox.Session):
|
|||
rust_flags = env.get("RUSTFLAGS", "")
|
||||
env["RUSTFLAGS"] = f"{rust_flags} -Dwarnings"
|
||||
|
||||
subcommand = "hack"
|
||||
if "minimal-versions" in session.posargs:
|
||||
subcommand = "minimal-versions"
|
||||
|
||||
comma_join = ",".join
|
||||
_run_cargo(
|
||||
session,
|
||||
subcommand,
|
||||
"hack",
|
||||
"--feature-powerset",
|
||||
'--optional-deps=""',
|
||||
f'--skip="{comma_join(features_to_skip)}"',
|
||||
|
@ -754,9 +744,7 @@ def update_ui_tests(session: nox.Session):
|
|||
|
||||
def _build_docs_for_ffi_check(session: nox.Session) -> None:
|
||||
# pyo3-ffi-check needs to scrape docs of pyo3-ffi
|
||||
env = os.environ.copy()
|
||||
env["PYO3_PYTHON"] = sys.executable
|
||||
_run_cargo(session, "doc", _FFI_CHECK, "-p", "pyo3-ffi", "--no-deps", env=env)
|
||||
_run_cargo(session, "doc", _FFI_CHECK, "-p", "pyo3-ffi", "--no-deps")
|
||||
|
||||
|
||||
@lru_cache()
|
||||
|
@ -784,9 +772,10 @@ def _get_rust_default_target() -> str:
|
|||
@lru_cache()
|
||||
def _get_feature_sets() -> Tuple[Tuple[str, ...], ...]:
|
||||
"""Returns feature sets to use for clippy job"""
|
||||
rust_version = _get_rust_version()
|
||||
cargo_target = os.getenv("CARGO_BUILD_TARGET", "")
|
||||
if "wasm32-wasi" not in cargo_target:
|
||||
# multiple-pymethods not supported on wasm
|
||||
if rust_version[:2] >= (1, 62) and "wasm32-wasi" not in cargo_target:
|
||||
# multiple-pymethods feature not supported before 1.62 or on WASI
|
||||
return (
|
||||
("--no-default-features",),
|
||||
(
|
||||
|
@ -927,9 +916,7 @@ class _ConfigFile:
|
|||
def __init__(self, config_file) -> None:
|
||||
self._config_file = config_file
|
||||
|
||||
def set(
|
||||
self, implementation: str, version: str, build_flags: Iterable[str] = ()
|
||||
) -> None:
|
||||
def set(self, implementation: str, version: str) -> None:
|
||||
"""Set the contents of this config file to the given implementation and version."""
|
||||
self._config_file.seek(0)
|
||||
self._config_file.truncate(0)
|
||||
|
@ -937,7 +924,6 @@ class _ConfigFile:
|
|||
f"""\
|
||||
implementation={implementation}
|
||||
version={version}
|
||||
build_flags={','.join(build_flags)}
|
||||
suppress_build_script_link_lines=true
|
||||
"""
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion};
|
||||
use codspeed_criterion_compat::{criterion_group, criterion_main, BatchSize, Bencher, Criterion};
|
||||
|
||||
use pyo3::prelude::*;
|
||||
|
||||
|
@ -9,8 +9,14 @@ fn bench_clean_acquire_gil(b: &mut Bencher<'_>) {
|
|||
|
||||
fn bench_dirty_acquire_gil(b: &mut Bencher<'_>) {
|
||||
let obj = Python::with_gil(|py| py.None());
|
||||
// Drop the returned clone of the object so that the reference pool has work to do.
|
||||
b.iter(|| Python::with_gil(|py| obj.clone_ref(py)));
|
||||
b.iter_batched(
|
||||
|| {
|
||||
// Clone and drop an object so that the GILPool has work to do.
|
||||
let _ = obj.clone();
|
||||
},
|
||||
|_| Python::with_gil(|_| {}),
|
||||
BatchSize::NumBatches(1),
|
||||
);
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
|
|
|
@ -1,12 +1,4 @@
|
|||
use codspeed_criterion_compat::{criterion_group, criterion_main, BatchSize, Bencher, Criterion};
|
||||
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
mpsc::channel,
|
||||
Arc, Barrier,
|
||||
};
|
||||
use std::thread::spawn;
|
||||
use std::time::{Duration, Instant};
|
||||
use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion};
|
||||
|
||||
use pyo3::prelude::*;
|
||||
|
||||
|
@ -14,108 +6,14 @@ fn drop_many_objects(b: &mut Bencher<'_>) {
|
|||
Python::with_gil(|py| {
|
||||
b.iter(|| {
|
||||
for _ in 0..1000 {
|
||||
drop(py.None());
|
||||
std::mem::drop(py.None());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn drop_many_objects_without_gil(b: &mut Bencher<'_>) {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
Python::with_gil(|py| {
|
||||
(0..1000)
|
||||
.map(|_| py.None().into_py(py))
|
||||
.collect::<Vec<PyObject>>()
|
||||
})
|
||||
},
|
||||
|objs| {
|
||||
drop(objs);
|
||||
|
||||
Python::with_gil(|_py| ());
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
}
|
||||
|
||||
fn drop_many_objects_multiple_threads(b: &mut Bencher<'_>) {
|
||||
const THREADS: usize = 5;
|
||||
|
||||
let barrier = Arc::new(Barrier::new(1 + THREADS));
|
||||
|
||||
let done = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let sender = (0..THREADS)
|
||||
.map(|_| {
|
||||
let (sender, receiver) = channel();
|
||||
|
||||
let barrier = barrier.clone();
|
||||
|
||||
let done = done.clone();
|
||||
|
||||
spawn(move || {
|
||||
for objs in receiver {
|
||||
barrier.wait();
|
||||
|
||||
drop(objs);
|
||||
|
||||
done.fetch_add(1, Ordering::AcqRel);
|
||||
}
|
||||
});
|
||||
|
||||
sender
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
b.iter_custom(|iters| {
|
||||
let mut duration = Duration::ZERO;
|
||||
|
||||
let mut last_done = done.load(Ordering::Acquire);
|
||||
|
||||
for _ in 0..iters {
|
||||
for sender in &sender {
|
||||
let objs = Python::with_gil(|py| {
|
||||
(0..1000 / THREADS)
|
||||
.map(|_| py.None().into_py(py))
|
||||
.collect::<Vec<PyObject>>()
|
||||
});
|
||||
|
||||
sender.send(objs).unwrap();
|
||||
}
|
||||
|
||||
barrier.wait();
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
loop {
|
||||
Python::with_gil(|_py| ());
|
||||
|
||||
let done = done.load(Ordering::Acquire);
|
||||
if done - last_done == THREADS {
|
||||
last_done = done;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Python::with_gil(|_py| ());
|
||||
|
||||
duration += start.elapsed();
|
||||
}
|
||||
|
||||
duration
|
||||
});
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("drop_many_objects", drop_many_objects);
|
||||
c.bench_function(
|
||||
"drop_many_objects_without_gil",
|
||||
drop_many_objects_without_gil,
|
||||
);
|
||||
c.bench_function(
|
||||
"drop_many_objects_multiple_threads",
|
||||
drop_many_objects_multiple_threads,
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.23.0-dev"
|
||||
version = "0.21.2"
|
||||
description = "Build configuration for the PyO3 ecosystem"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||
|
@ -13,11 +13,11 @@ edition = "2021"
|
|||
[dependencies]
|
||||
once_cell = "1"
|
||||
python3-dll-a = { version = "0.2.6", optional = true }
|
||||
target-lexicon = "0.12.14"
|
||||
target-lexicon = "0.12"
|
||||
|
||||
[build-dependencies]
|
||||
python3-dll-a = { version = "0.2.6", optional = true }
|
||||
target-lexicon = "0.12.14"
|
||||
target-lexicon = "0.12"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
|
@ -26,11 +26,11 @@ use target_lexicon::{Environment, OperatingSystem};
|
|||
use crate::{
|
||||
bail, ensure,
|
||||
errors::{Context, Error, Result},
|
||||
warn,
|
||||
format_warn, warn,
|
||||
};
|
||||
|
||||
/// Minimum Python version PyO3 supports.
|
||||
pub(crate) const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 7 };
|
||||
const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 7 };
|
||||
|
||||
/// GraalPy may implement the same CPython version over multiple releases.
|
||||
const MINIMUM_SUPPORTED_VERSION_GRAALPY: PythonVersion = PythonVersion {
|
||||
|
@ -39,7 +39,7 @@ const MINIMUM_SUPPORTED_VERSION_GRAALPY: PythonVersion = PythonVersion {
|
|||
};
|
||||
|
||||
/// Maximum Python version that can be used as minimum required Python version with abi3.
|
||||
pub(crate) const ABI3_MAX_MINOR: u8 = 12;
|
||||
const ABI3_MAX_MINOR: u8 = 12;
|
||||
|
||||
/// Gets an environment variable owned by cargo.
|
||||
///
|
||||
|
@ -171,13 +171,20 @@ impl InterpreterConfig {
|
|||
out.push(format!("cargo:rustc-cfg=Py_3_{}", i));
|
||||
}
|
||||
|
||||
match self.implementation {
|
||||
PythonImplementation::CPython => {}
|
||||
PythonImplementation::PyPy => out.push("cargo:rustc-cfg=PyPy".to_owned()),
|
||||
PythonImplementation::GraalPy => out.push("cargo:rustc-cfg=GraalPy".to_owned()),
|
||||
}
|
||||
|
||||
if self.abi3 {
|
||||
if self.implementation.is_pypy() {
|
||||
out.push("cargo:rustc-cfg=PyPy".to_owned());
|
||||
if self.abi3 {
|
||||
out.push(format_warn!(
|
||||
"PyPy does not yet support abi3 so the build artifacts will be version-specific. \
|
||||
See https://foss.heptapod.net/pypy/pypy/-/issues/3397 for more information."
|
||||
));
|
||||
}
|
||||
} else if self.implementation.is_graalpy() {
|
||||
println!("cargo:rustc-cfg=GraalPy");
|
||||
if self.abi3 {
|
||||
warn!("GraalPy does not support abi3 so the build artifacts will be version-specific.");
|
||||
}
|
||||
} else if self.abi3 {
|
||||
out.push("cargo:rustc-cfg=Py_LIMITED_API".to_owned());
|
||||
}
|
||||
|
||||
|
@ -959,11 +966,11 @@ impl CrossCompileEnvVars {
|
|||
///
|
||||
/// This function relies on PyO3 cross-compiling environment variables:
|
||||
///
|
||||
/// * `PYO3_CROSS`: If present, forces PyO3 to configure as a cross-compilation.
|
||||
/// * `PYO3_CROSS_LIB_DIR`: If present, must be set to the directory containing
|
||||
/// * `PYO3_CROSS`: If present, forces PyO3 to configure as a cross-compilation.
|
||||
/// * `PYO3_CROSS_LIB_DIR`: If present, must be set to the directory containing
|
||||
/// the target's libpython DSO and the associated `_sysconfigdata*.py` file for
|
||||
/// Unix-like targets, or the Python DLL import libraries for the Windows target.
|
||||
/// * `PYO3_CROSS_PYTHON_VERSION`: Major and minor version (e.g. 3.9) of the target Python
|
||||
/// * `PYO3_CROSS_PYTHON_VERSION`: Major and minor version (e.g. 3.9) of the target Python
|
||||
/// installation. This variable is only needed if PyO3 cannnot determine the version to target
|
||||
/// from `abi3-py3*` features, or if there are multiple versions of Python present in
|
||||
/// `PYO3_CROSS_LIB_DIR`.
|
||||
|
@ -996,7 +1003,6 @@ pub enum BuildFlag {
|
|||
Py_DEBUG,
|
||||
Py_REF_DEBUG,
|
||||
Py_TRACE_REFS,
|
||||
Py_GIL_DISABLED,
|
||||
COUNT_ALLOCS,
|
||||
Other(String),
|
||||
}
|
||||
|
@ -1017,7 +1023,6 @@ impl FromStr for BuildFlag {
|
|||
"Py_DEBUG" => Ok(BuildFlag::Py_DEBUG),
|
||||
"Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG),
|
||||
"Py_TRACE_REFS" => Ok(BuildFlag::Py_TRACE_REFS),
|
||||
"Py_GIL_DISABLED" => Ok(BuildFlag::Py_GIL_DISABLED),
|
||||
"COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS),
|
||||
other => Ok(BuildFlag::Other(other.to_owned())),
|
||||
}
|
||||
|
@ -1041,11 +1046,10 @@ impl FromStr for BuildFlag {
|
|||
pub struct BuildFlags(pub HashSet<BuildFlag>);
|
||||
|
||||
impl BuildFlags {
|
||||
const ALL: [BuildFlag; 5] = [
|
||||
const ALL: [BuildFlag; 4] = [
|
||||
BuildFlag::Py_DEBUG,
|
||||
BuildFlag::Py_REF_DEBUG,
|
||||
BuildFlag::Py_TRACE_REFS,
|
||||
BuildFlag::Py_GIL_DISABLED,
|
||||
BuildFlag::COUNT_ALLOCS,
|
||||
];
|
||||
|
||||
|
@ -1059,7 +1063,7 @@ impl BuildFlags {
|
|||
.iter()
|
||||
.filter(|flag| {
|
||||
config_map
|
||||
.get_value(flag.to_string())
|
||||
.get_value(&flag.to_string())
|
||||
.map_or(false, |value| value == "1")
|
||||
})
|
||||
.cloned()
|
||||
|
@ -1209,7 +1213,7 @@ fn ends_with(entry: &DirEntry, pat: &str) -> bool {
|
|||
/// Returns `None` if the library directory is not available, and a runtime error
|
||||
/// when no or multiple sysconfigdata files are found.
|
||||
fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result<Option<PathBuf>> {
|
||||
let mut sysconfig_paths = find_all_sysconfigdata(cross)?;
|
||||
let mut sysconfig_paths = find_all_sysconfigdata(cross);
|
||||
if sysconfig_paths.is_empty() {
|
||||
if let Some(lib_dir) = cross.lib_dir.as_ref() {
|
||||
bail!("Could not find _sysconfigdata*.py in {}", lib_dir.display());
|
||||
|
@ -1272,16 +1276,11 @@ fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result<Option<PathBuf>> {
|
|||
///
|
||||
/// Returns an empty vector when the target Python library directory
|
||||
/// is not set via `PYO3_CROSS_LIB_DIR`.
|
||||
pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
|
||||
pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Vec<PathBuf> {
|
||||
let sysconfig_paths = if let Some(lib_dir) = cross.lib_dir.as_ref() {
|
||||
search_lib_dir(lib_dir, cross).with_context(|| {
|
||||
format!(
|
||||
"failed to search the lib dir at 'PYO3_CROSS_LIB_DIR={}'",
|
||||
lib_dir.display()
|
||||
)
|
||||
})?
|
||||
search_lib_dir(lib_dir, cross)
|
||||
} else {
|
||||
return Ok(Vec::new());
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let sysconfig_name = env_var("_PYTHON_SYSCONFIGDATA_NAME");
|
||||
|
@ -1299,7 +1298,7 @@ pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Result<Vec<PathBuf>
|
|||
sysconfig_paths.sort();
|
||||
sysconfig_paths.dedup();
|
||||
|
||||
Ok(sysconfig_paths)
|
||||
sysconfig_paths
|
||||
}
|
||||
|
||||
fn is_pypy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
|
||||
|
@ -1330,14 +1329,9 @@ fn is_cpython_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
|
|||
}
|
||||
|
||||
/// recursive search for _sysconfigdata, returns all possibilities of sysconfigdata paths
|
||||
fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
|
||||
fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<PathBuf> {
|
||||
let mut sysconfig_paths = vec![];
|
||||
for f in fs::read_dir(path.as_ref()).with_context(|| {
|
||||
format!(
|
||||
"failed to list the entries in '{}'",
|
||||
path.as_ref().display()
|
||||
)
|
||||
})? {
|
||||
for f in fs::read_dir(path).expect("Path does not exist") {
|
||||
sysconfig_paths.extend(match &f {
|
||||
// Python 3.7+ sysconfigdata with platform specifics
|
||||
Ok(f) if starts_with(f, "_sysconfigdata_") && ends_with(f, "py") => vec![f.path()],
|
||||
|
@ -1345,7 +1339,7 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Result<
|
|||
let file_name = f.file_name();
|
||||
let file_name = file_name.to_string_lossy();
|
||||
if file_name == "build" || file_name == "lib" {
|
||||
search_lib_dir(f.path(), cross)?
|
||||
search_lib_dir(f.path(), cross)
|
||||
} else if file_name.starts_with("lib.") {
|
||||
// check if right target os
|
||||
if !file_name.contains(&cross.target.operating_system.to_string()) {
|
||||
|
@ -1355,12 +1349,12 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Result<
|
|||
if !file_name.contains(&cross.target.architecture.to_string()) {
|
||||
continue;
|
||||
}
|
||||
search_lib_dir(f.path(), cross)?
|
||||
search_lib_dir(f.path(), cross)
|
||||
} else if is_cpython_lib_dir(&file_name, &cross.version)
|
||||
|| is_pypy_lib_dir(&file_name, &cross.version)
|
||||
|| is_graalpy_lib_dir(&file_name, &cross.version)
|
||||
{
|
||||
search_lib_dir(f.path(), cross)?
|
||||
search_lib_dir(f.path(), cross)
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
@ -1389,7 +1383,7 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Result<
|
|||
}
|
||||
}
|
||||
|
||||
Ok(sysconfig_paths)
|
||||
sysconfig_paths
|
||||
}
|
||||
|
||||
/// Find cross compilation information from sysconfigdata file
|
||||
|
@ -2728,7 +2722,10 @@ mod tests {
|
|||
"cargo:rustc-cfg=Py_3_6".to_owned(),
|
||||
"cargo:rustc-cfg=Py_3_7".to_owned(),
|
||||
"cargo:rustc-cfg=PyPy".to_owned(),
|
||||
"cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
|
||||
"cargo:warning=PyPy does not yet support abi3 so the build artifacts \
|
||||
will be version-specific. See https://foss.heptapod.net/pypy/pypy/-/issues/3397 \
|
||||
for more information."
|
||||
.to_owned(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -2760,24 +2757,4 @@ mod tests {
|
|||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_sysconfigdata_in_invalid_lib_dir() {
|
||||
let e = find_all_sysconfigdata(&CrossCompileConfig {
|
||||
lib_dir: Some(PathBuf::from("/abc/123/not/a/real/path")),
|
||||
version: None,
|
||||
implementation: None,
|
||||
target: triple!("x86_64-unknown-linux-gnu"),
|
||||
})
|
||||
.unwrap_err();
|
||||
|
||||
// actual error message is platform-dependent, so just check the context we add
|
||||
assert!(e.report().to_string().starts_with(
|
||||
"failed to search the lib dir at 'PYO3_CROSS_LIB_DIR=/abc/123/not/a/real/path'\n\
|
||||
caused by:\n \
|
||||
- 0: failed to list the entries in '/abc/123/not/a/real/path'\n \
|
||||
- 1: \
|
||||
"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ use std::{
|
|||
|
||||
use std::{env, process::Command, str::FromStr};
|
||||
|
||||
#[cfg(feature = "resolve-config")]
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
pub use impl_::{
|
||||
|
@ -39,12 +40,9 @@ use target_lexicon::OperatingSystem;
|
|||
/// | `#[cfg(PyPy)]` | This marks code which is run when compiling for PyPy. |
|
||||
/// | `#[cfg(GraalPy)]` | This marks code which is run when compiling for GraalPy. |
|
||||
///
|
||||
/// For examples of how to use these attributes,
|
||||
#[doc = concat!("[see PyO3's guide](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution/multiple_python_versions.html)")]
|
||||
/// .
|
||||
/// For examples of how to use these attributes, [see PyO3's guide](https://pyo3.rs/latest/building-and-distribution/multiple_python_versions.html).
|
||||
#[cfg(feature = "resolve-config")]
|
||||
pub fn use_pyo3_cfgs() {
|
||||
print_expected_cfgs();
|
||||
for cargo_command in get().build_script_outputs() {
|
||||
println!("{}", cargo_command)
|
||||
}
|
||||
|
@ -88,8 +86,6 @@ pub fn get() -> &'static InterpreterConfig {
|
|||
.map(|path| path.exists())
|
||||
.unwrap_or(false);
|
||||
|
||||
// CONFIG_FILE is generated in build.rs, so it's content can vary
|
||||
#[allow(unknown_lints, clippy::const_is_empty)]
|
||||
if let Some(interpreter_config) = InterpreterConfig::from_cargo_dep_env() {
|
||||
interpreter_config
|
||||
} else if !CONFIG_FILE.is_empty() {
|
||||
|
@ -136,50 +132,28 @@ fn resolve_cross_compile_config_path() -> Option<PathBuf> {
|
|||
/// so this function is unstable.
|
||||
#[doc(hidden)]
|
||||
pub fn print_feature_cfgs() {
|
||||
fn rustc_minor_version() -> Option<u32> {
|
||||
let rustc = env::var_os("RUSTC")?;
|
||||
let output = Command::new(rustc).arg("--version").output().ok()?;
|
||||
let version = core::str::from_utf8(&output.stdout).ok()?;
|
||||
let mut pieces = version.split('.');
|
||||
if pieces.next() != Some("rustc 1") {
|
||||
return None;
|
||||
}
|
||||
pieces.next()?.parse().ok()
|
||||
}
|
||||
|
||||
let rustc_minor_version = rustc_minor_version().unwrap_or(0);
|
||||
|
||||
// Enable use of const initializer for thread_local! on Rust 1.59 and greater
|
||||
if rustc_minor_version >= 59 {
|
||||
println!("cargo:rustc-cfg=thread_local_const_init");
|
||||
}
|
||||
|
||||
// invalid_from_utf8 lint was added in Rust 1.74
|
||||
if rustc_minor_version >= 74 {
|
||||
println!("cargo:rustc-cfg=invalid_from_utf8_lint");
|
||||
}
|
||||
|
||||
if rustc_minor_version >= 77 {
|
||||
println!("cargo:rustc-cfg=c_str_lit");
|
||||
}
|
||||
|
||||
// Actually this is available on 1.78, but we should avoid
|
||||
// https://github.com/rust-lang/rust/issues/124651 just in case
|
||||
if rustc_minor_version >= 79 {
|
||||
println!("cargo:rustc-cfg=diagnostic_namespace");
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers `pyo3`s config names as reachable cfg expressions
|
||||
///
|
||||
/// - <https://github.com/rust-lang/cargo/pull/13571>
|
||||
/// - <https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#rustc-check-cfg>
|
||||
#[doc(hidden)]
|
||||
pub fn print_expected_cfgs() {
|
||||
if rustc_minor_version().map_or(false, |version| version < 80) {
|
||||
// rustc 1.80.0 stabilized `rustc-check-cfg` feature, don't emit before
|
||||
return;
|
||||
}
|
||||
|
||||
println!("cargo:rustc-check-cfg=cfg(Py_LIMITED_API)");
|
||||
println!("cargo:rustc-check-cfg=cfg(PyPy)");
|
||||
println!("cargo:rustc-check-cfg=cfg(GraalPy)");
|
||||
println!("cargo:rustc-check-cfg=cfg(py_sys_config, values(\"Py_DEBUG\", \"Py_REF_DEBUG\", \"Py_TRACE_REFS\", \"COUNT_ALLOCS\"))");
|
||||
println!("cargo:rustc-check-cfg=cfg(invalid_from_utf8_lint)");
|
||||
println!("cargo:rustc-check-cfg=cfg(pyo3_disable_reference_pool)");
|
||||
println!("cargo:rustc-check-cfg=cfg(pyo3_leak_on_drop_without_reference_pool)");
|
||||
println!("cargo:rustc-check-cfg=cfg(diagnostic_namespace)");
|
||||
println!("cargo:rustc-check-cfg=cfg(c_str_lit)");
|
||||
|
||||
// allow `Py_3_*` cfgs from the minimum supported version up to the
|
||||
// maximum minor version (+1 for development for the next)
|
||||
for i in impl_::MINIMUM_SUPPORTED_VERSION.minor..=impl_::ABI3_MAX_MINOR + 1 {
|
||||
println!("cargo:rustc-check-cfg=cfg(Py_3_{i})");
|
||||
}
|
||||
}
|
||||
|
||||
/// Private exports used in PyO3's build.rs
|
||||
|
@ -208,8 +182,6 @@ pub mod pyo3_build_script_impl {
|
|||
/// correct value for CARGO_CFG_TARGET_OS).
|
||||
#[cfg(feature = "resolve-config")]
|
||||
pub fn resolve_interpreter_config() -> Result<InterpreterConfig> {
|
||||
// CONFIG_FILE is generated in build.rs, so it's content can vary
|
||||
#[allow(unknown_lints, clippy::const_is_empty)]
|
||||
if !CONFIG_FILE.is_empty() {
|
||||
let mut interperter_config = InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE))?;
|
||||
interperter_config.generate_import_libs()?;
|
||||
|
@ -240,20 +212,6 @@ pub mod pyo3_build_script_impl {
|
|||
}
|
||||
}
|
||||
|
||||
fn rustc_minor_version() -> Option<u32> {
|
||||
static RUSTC_MINOR_VERSION: OnceCell<Option<u32>> = OnceCell::new();
|
||||
*RUSTC_MINOR_VERSION.get_or_init(|| {
|
||||
let rustc = env::var_os("RUSTC")?;
|
||||
let output = Command::new(rustc).arg("--version").output().ok()?;
|
||||
let version = core::str::from_utf8(&output.stdout).ok()?;
|
||||
let mut pieces = version.split('.');
|
||||
if pieces.next() != Some("rustc 1") {
|
||||
return None;
|
||||
}
|
||||
pieces.next()?.parse().ok()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -10,6 +10,6 @@ proc-macro = true
|
|||
[dependencies]
|
||||
glob = "0.3"
|
||||
quote = "1"
|
||||
proc-macro2 = "1.0.60"
|
||||
proc-macro2 = "1"
|
||||
scraper = "0.17"
|
||||
pyo3-build-config = { path = "../../pyo3-build-config" }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.23.0-dev"
|
||||
version = "0.21.2"
|
||||
description = "Python-API bindings for the PyO3 ecosystem"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||
|
@ -38,7 +38,7 @@ abi3-py312 = ["abi3", "pyo3-build-config/abi3-py312"]
|
|||
generate-import-lib = ["pyo3-build-config/python3-dll-a"]
|
||||
|
||||
[build-dependencies]
|
||||
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.0-dev", features = ["resolve-config"] }
|
||||
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.21.2", features = ["resolve-config"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
@ -14,7 +14,7 @@ Manual][capi] for up-to-date documentation.
|
|||
|
||||
PyO3 supports the following software versions:
|
||||
- Python 3.7 and up (CPython and PyPy)
|
||||
- Rust 1.63 and up
|
||||
- Rust 1.56 and up
|
||||
|
||||
# Example: Building Python Native modules
|
||||
|
||||
|
@ -51,8 +51,10 @@ use pyo3_ffi::*;
|
|||
|
||||
static mut MODULE_DEF: PyModuleDef = PyModuleDef {
|
||||
m_base: PyModuleDef_HEAD_INIT,
|
||||
m_name: c_str!("string_sum").as_ptr(),
|
||||
m_doc: c_str!("A Python module written in Rust.").as_ptr(),
|
||||
m_name: "string_sum\0".as_ptr().cast::<c_char>(),
|
||||
m_doc: "A Python module written in Rust.\0"
|
||||
.as_ptr()
|
||||
.cast::<c_char>(),
|
||||
m_size: 0,
|
||||
m_methods: unsafe { METHODS.as_mut_ptr().cast() },
|
||||
m_slots: std::ptr::null_mut(),
|
||||
|
@ -63,12 +65,14 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef {
|
|||
|
||||
static mut METHODS: [PyMethodDef; 2] = [
|
||||
PyMethodDef {
|
||||
ml_name: c_str!("sum_as_string").as_ptr(),
|
||||
ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(),
|
||||
ml_meth: PyMethodDefPointer {
|
||||
_PyCFunctionFast: sum_as_string,
|
||||
},
|
||||
ml_flags: METH_FASTCALL,
|
||||
ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
|
||||
ml_doc: "returns the sum of two integers as a string\0"
|
||||
.as_ptr()
|
||||
.cast::<c_char>(),
|
||||
},
|
||||
// A zeroed PyMethodDef to mark the end of the array.
|
||||
PyMethodDef::zeroed()
|
||||
|
@ -89,7 +93,9 @@ pub unsafe extern "C" fn sum_as_string(
|
|||
if nargs != 2 {
|
||||
PyErr_SetString(
|
||||
PyExc_TypeError,
|
||||
c_str!("sum_as_string() expected 2 positional arguments").as_ptr(),
|
||||
"sum_as_string() expected 2 positional arguments\0"
|
||||
.as_ptr()
|
||||
.cast::<c_char>(),
|
||||
);
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
@ -98,7 +104,9 @@ pub unsafe extern "C" fn sum_as_string(
|
|||
if PyLong_Check(arg1) == 0 {
|
||||
PyErr_SetString(
|
||||
PyExc_TypeError,
|
||||
c_str!("sum_as_string() expected an int for positional argument 1").as_ptr(),
|
||||
"sum_as_string() expected an int for positional argument 1\0"
|
||||
.as_ptr()
|
||||
.cast::<c_char>(),
|
||||
);
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
@ -112,7 +120,9 @@ pub unsafe extern "C" fn sum_as_string(
|
|||
if PyLong_Check(arg2) == 0 {
|
||||
PyErr_SetString(
|
||||
PyExc_TypeError,
|
||||
c_str!("sum_as_string() expected an int for positional argument 2").as_ptr(),
|
||||
"sum_as_string() expected an int for positional argument 2\0"
|
||||
.as_ptr()
|
||||
.cast::<c_char>(),
|
||||
);
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
@ -130,7 +140,7 @@ pub unsafe extern "C" fn sum_as_string(
|
|||
None => {
|
||||
PyErr_SetString(
|
||||
PyExc_OverflowError,
|
||||
c_str!("arguments too large to add").as_ptr(),
|
||||
"arguments too large to add\0".as_ptr().cast::<c_char>(),
|
||||
);
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
|
|
@ -4,9 +4,8 @@ use pyo3_build_config::{
|
|||
cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config,
|
||||
InterpreterConfig, PythonVersion,
|
||||
},
|
||||
warn, BuildFlag, PythonImplementation,
|
||||
PythonImplementation,
|
||||
};
|
||||
use std::ops::Not;
|
||||
|
||||
/// Minimum Python version PyO3 supports.
|
||||
struct SupportedVersions {
|
||||
|
@ -18,7 +17,7 @@ const SUPPORTED_VERSIONS_CPYTHON: SupportedVersions = SupportedVersions {
|
|||
min: PythonVersion { major: 3, minor: 7 },
|
||||
max: PythonVersion {
|
||||
major: 3,
|
||||
minor: 13,
|
||||
minor: 12,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -105,37 +104,6 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
if interpreter_config.abi3 {
|
||||
match interpreter_config.implementation {
|
||||
PythonImplementation::CPython => {}
|
||||
PythonImplementation::PyPy => warn!(
|
||||
"PyPy does not yet support abi3 so the build artifacts will be version-specific. \
|
||||
See https://foss.heptapod.net/pypy/pypy/-/issues/3397 for more information."
|
||||
),
|
||||
PythonImplementation::GraalPy => warn!(
|
||||
"GraalPy does not support abi3 so the build artifacts will be version-specific."
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_gil_enabled(interpreter_config: &InterpreterConfig) -> Result<()> {
|
||||
let gil_enabled = interpreter_config
|
||||
.build_flags
|
||||
.0
|
||||
.contains(&BuildFlag::Py_GIL_DISABLED)
|
||||
.not();
|
||||
ensure!(
|
||||
gil_enabled || std::env::var("UNSAFE_PYO3_BUILD_FREE_THREADED").map_or(false, |os_str| os_str == "1"),
|
||||
"the Python interpreter was built with the GIL disabled, which is not yet supported by PyO3\n\
|
||||
= help: see https://github.com/PyO3/pyo3/issues/4265 for more information\n\
|
||||
= help: please check if an updated version of PyO3 is available. Current version: {}\n\
|
||||
= help: set UNSAFE_PYO3_BUILD_FREE_THREADED=1 to suppress this check and build anyway for free-threaded Python",
|
||||
std::env::var("CARGO_PKG_VERSION").unwrap()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -204,7 +172,6 @@ fn configure_pyo3() -> Result<()> {
|
|||
|
||||
ensure_python_version(&interpreter_config)?;
|
||||
ensure_target_pointer_width(&interpreter_config)?;
|
||||
ensure_gil_enabled(&interpreter_config)?;
|
||||
|
||||
// Serialize the whole interpreter config into DEP_PYTHON_PYO3_CONFIG env var.
|
||||
interpreter_config.to_cargo_dep_env()?;
|
||||
|
@ -222,7 +189,7 @@ fn configure_pyo3() -> Result<()> {
|
|||
println!("{}", line);
|
||||
}
|
||||
|
||||
// Emit cfgs like `invalid_from_utf8_lint`
|
||||
// Emit cfgs like `thread_local_const_init`
|
||||
print_feature_cfgs();
|
||||
|
||||
Ok(())
|
||||
|
@ -238,7 +205,6 @@ fn print_config_and_exit(config: &InterpreterConfig) {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
pyo3_build_config::print_expected_cfgs();
|
||||
if let Err(e) = configure_pyo3() {
|
||||
eprintln!("error: {}", e.report());
|
||||
std::process::exit(1)
|
||||
|
|
|
@ -114,7 +114,10 @@ extern "C" {
|
|||
#[cfg(not(any(Py_3_8, PyPy)))]
|
||||
#[inline]
|
||||
pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int {
|
||||
crate::PyObject_HasAttrString(crate::Py_TYPE(o).cast(), c_str!("__next__").as_ptr())
|
||||
crate::PyObject_HasAttrString(
|
||||
crate::Py_TYPE(o).cast(),
|
||||
"__next__\0".as_ptr() as *const c_char,
|
||||
)
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -4,6 +4,8 @@ use std::os::raw::{c_char, c_int};
|
|||
#[cfg(not(Py_3_11))]
|
||||
use crate::Py_buffer;
|
||||
|
||||
#[cfg(Py_3_8)]
|
||||
use crate::pyport::PY_SSIZE_T_MAX;
|
||||
#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))]
|
||||
use crate::{
|
||||
vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET, PyTuple_Check,
|
||||
|
@ -40,14 +42,14 @@ extern "C" {
|
|||
}
|
||||
|
||||
#[cfg(Py_3_8)]
|
||||
const PY_VECTORCALL_ARGUMENTS_OFFSET: size_t =
|
||||
1 << (8 * std::mem::size_of::<size_t>() as size_t - 1);
|
||||
const PY_VECTORCALL_ARGUMENTS_OFFSET: Py_ssize_t =
|
||||
1 << (8 * std::mem::size_of::<Py_ssize_t>() as Py_ssize_t - 1);
|
||||
|
||||
#[cfg(Py_3_8)]
|
||||
#[inline(always)]
|
||||
pub unsafe fn PyVectorcall_NARGS(n: size_t) -> Py_ssize_t {
|
||||
let n = n & !PY_VECTORCALL_ARGUMENTS_OFFSET;
|
||||
n.try_into().expect("cannot fail due to mask")
|
||||
assert!(n <= (PY_SSIZE_T_MAX as size_t));
|
||||
(n as Py_ssize_t) & !PY_VECTORCALL_ARGUMENTS_OFFSET
|
||||
}
|
||||
|
||||
#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))]
|
||||
|
@ -61,7 +63,7 @@ pub unsafe fn PyVectorcall_Function(callable: *mut PyObject) -> Option<vectorcal
|
|||
assert!(PyCallable_Check(callable) > 0);
|
||||
let offset = (*tp).tp_vectorcall_offset;
|
||||
assert!(offset > 0);
|
||||
let ptr = callable.cast::<c_char>().offset(offset).cast();
|
||||
let ptr = (callable as *const c_char).offset(offset) as *const Option<vectorcallfunc>;
|
||||
*ptr
|
||||
}
|
||||
|
||||
|
@ -182,7 +184,7 @@ pub unsafe fn PyObject_CallOneArg(func: *mut PyObject, arg: *mut PyObject) -> *m
|
|||
let args = args_array.as_ptr().offset(1); // For PY_VECTORCALL_ARGUMENTS_OFFSET
|
||||
let tstate = PyThreadState_GET();
|
||||
let nargsf = 1 | PY_VECTORCALL_ARGUMENTS_OFFSET;
|
||||
_PyObject_VectorcallTstate(tstate, func, args, nargsf, std::ptr::null_mut())
|
||||
_PyObject_VectorcallTstate(tstate, func, args, nargsf as size_t, std::ptr::null_mut())
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
@ -204,7 +206,7 @@ pub unsafe fn PyObject_CallMethodNoArgs(
|
|||
PyObject_VectorcallMethod(
|
||||
name,
|
||||
&self_,
|
||||
1 | PY_VECTORCALL_ARGUMENTS_OFFSET,
|
||||
1 | PY_VECTORCALL_ARGUMENTS_OFFSET as size_t,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
}
|
||||
|
@ -221,7 +223,7 @@ pub unsafe fn PyObject_CallMethodOneArg(
|
|||
PyObject_VectorcallMethod(
|
||||
name,
|
||||
args.as_ptr(),
|
||||
2 | PY_VECTORCALL_ARGUMENTS_OFFSET,
|
||||
2 | PY_VECTORCALL_ARGUMENTS_OFFSET as size_t,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -20,11 +20,7 @@ pub const _PY_MONITORING_EVENTS: usize = 17;
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct _Py_LocalMonitors {
|
||||
pub tools: [u8; if cfg!(Py_3_13) {
|
||||
_PY_MONITORING_LOCAL_EVENTS
|
||||
} else {
|
||||
_PY_MONITORING_UNGROUPED_EVENTS
|
||||
}],
|
||||
pub tools: [u8; _PY_MONITORING_UNGROUPED_EVENTS],
|
||||
}
|
||||
|
||||
#[cfg(Py_3_12)]
|
||||
|
@ -106,9 +102,6 @@ pub struct PyCodeObject {
|
|||
pub co_extra: *mut c_void,
|
||||
}
|
||||
|
||||
#[cfg(Py_3_13)]
|
||||
opaque_struct!(_PyExecutorArray);
|
||||
|
||||
#[cfg(all(not(any(PyPy, GraalPy)), Py_3_8, not(Py_3_11)))]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -183,8 +176,6 @@ pub struct PyCodeObject {
|
|||
pub _co_code: *mut PyObject,
|
||||
#[cfg(not(Py_3_12))]
|
||||
pub _co_linearray: *mut c_char,
|
||||
#[cfg(Py_3_13)]
|
||||
pub co_executors: *mut _PyExecutorArray,
|
||||
#[cfg(Py_3_12)]
|
||||
pub _co_cached: *mut _PyCoCached,
|
||||
#[cfg(Py_3_12)]
|
||||
|
|
|
@ -30,7 +30,7 @@ pub struct PyCompilerFlags {
|
|||
|
||||
// skipped non-limited _PyCompilerFlags_INIT
|
||||
|
||||
#[cfg(all(Py_3_12, not(any(Py_3_13, PyPy, GraalPy))))]
|
||||
#[cfg(all(Py_3_12, not(any(PyPy, GraalPy))))]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct _PyCompilerSrcLocation {
|
||||
|
@ -42,7 +42,7 @@ pub struct _PyCompilerSrcLocation {
|
|||
|
||||
// skipped SRC_LOCATION_FROM_AST
|
||||
|
||||
#[cfg(not(any(PyPy, GraalPy, Py_3_13)))]
|
||||
#[cfg(not(any(PyPy, GraalPy)))]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct PyFutureFeatures {
|
||||
|
|
|
@ -57,7 +57,7 @@ pub struct _frozen {
|
|||
pub size: c_int,
|
||||
#[cfg(Py_3_11)]
|
||||
pub is_package: c_int,
|
||||
#[cfg(all(Py_3_11, not(Py_3_13)))]
|
||||
#[cfg(Py_3_11)]
|
||||
pub get_code: Option<unsafe extern "C" fn() -> *mut PyObject>,
|
||||
}
|
||||
|
||||
|
|
|
@ -141,8 +141,6 @@ pub struct PyConfig {
|
|||
pub safe_path: c_int,
|
||||
#[cfg(Py_3_12)]
|
||||
pub int_max_str_digits: c_int,
|
||||
#[cfg(Py_3_13)]
|
||||
pub cpu_count: c_int,
|
||||
pub pathconfig_warnings: c_int,
|
||||
#[cfg(Py_3_10)]
|
||||
pub program_name: *mut wchar_t,
|
||||
|
@ -167,8 +165,6 @@ pub struct PyConfig {
|
|||
pub run_command: *mut wchar_t,
|
||||
pub run_module: *mut wchar_t,
|
||||
pub run_filename: *mut wchar_t,
|
||||
#[cfg(Py_3_13)]
|
||||
pub sys_path_0: *mut wchar_t,
|
||||
pub _install_importlib: c_int,
|
||||
pub _init_main: c_int,
|
||||
#[cfg(all(Py_3_9, not(Py_3_12)))]
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
use crate::longobject::*;
|
||||
use crate::object::*;
|
||||
#[cfg(Py_3_13)]
|
||||
use crate::pyport::Py_ssize_t;
|
||||
use libc::size_t;
|
||||
#[cfg(Py_3_13)]
|
||||
use std::os::raw::c_void;
|
||||
use std::os::raw::{c_int, c_uchar};
|
||||
|
||||
#[cfg(Py_3_13)]
|
||||
extern "C" {
|
||||
pub fn PyLong_FromUnicodeObject(u: *mut PyObject, base: c_int) -> *mut PyObject;
|
||||
}
|
||||
|
||||
#[cfg(Py_3_13)]
|
||||
pub const Py_ASNATIVEBYTES_DEFAULTS: c_int = -1;
|
||||
#[cfg(Py_3_13)]
|
||||
pub const Py_ASNATIVEBYTES_BIG_ENDIAN: c_int = 0;
|
||||
#[cfg(Py_3_13)]
|
||||
pub const Py_ASNATIVEBYTES_LITTLE_ENDIAN: c_int = 1;
|
||||
#[cfg(Py_3_13)]
|
||||
pub const Py_ASNATIVEBYTES_NATIVE_ENDIAN: c_int = 3;
|
||||
#[cfg(Py_3_13)]
|
||||
pub const Py_ASNATIVEBYTES_UNSIGNED_BUFFER: c_int = 4;
|
||||
#[cfg(Py_3_13)]
|
||||
pub const Py_ASNATIVEBYTES_REJECT_NEGATIVE: c_int = 8;
|
||||
|
||||
extern "C" {
|
||||
// skipped _PyLong_Sign
|
||||
|
||||
#[cfg(Py_3_13)]
|
||||
pub fn PyLong_AsNativeBytes(
|
||||
v: *mut PyObject,
|
||||
buffer: *mut c_void,
|
||||
n_bytes: Py_ssize_t,
|
||||
flags: c_int,
|
||||
) -> Py_ssize_t;
|
||||
|
||||
#[cfg(Py_3_13)]
|
||||
pub fn PyLong_FromNativeBytes(
|
||||
buffer: *const c_void,
|
||||
n_bytes: size_t,
|
||||
flags: c_int,
|
||||
) -> *mut PyObject;
|
||||
|
||||
#[cfg(Py_3_13)]
|
||||
pub fn PyLong_FromUnsignedNativeBytes(
|
||||
buffer: *const c_void,
|
||||
n_bytes: size_t,
|
||||
flags: c_int,
|
||||
) -> *mut PyObject;
|
||||
|
||||
// skipped PyUnstable_Long_IsCompact
|
||||
// skipped PyUnstable_Long_CompactValue
|
||||
|
||||
#[cfg_attr(PyPy, link_name = "_PyPyLong_FromByteArray")]
|
||||
pub fn _PyLong_FromByteArray(
|
||||
bytes: *const c_uchar,
|
||||
n: size_t,
|
||||
little_endian: c_int,
|
||||
is_signed: c_int,
|
||||
) -> *mut PyObject;
|
||||
|
||||
#[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")]
|
||||
pub fn _PyLong_AsByteArray(
|
||||
v: *mut PyLongObject,
|
||||
bytes: *mut c_uchar,
|
||||
n: size_t,
|
||||
little_endian: c_int,
|
||||
is_signed: c_int,
|
||||
) -> c_int;
|
||||
|
||||
// skipped _PyLong_GCD
|
||||
}
|
|
@ -18,7 +18,6 @@ pub(crate) mod import;
|
|||
pub(crate) mod initconfig;
|
||||
// skipped interpreteridobject.h
|
||||
pub(crate) mod listobject;
|
||||
pub(crate) mod longobject;
|
||||
#[cfg(all(Py_3_9, not(PyPy)))]
|
||||
pub(crate) mod methodobject;
|
||||
pub(crate) mod object;
|
||||
|
@ -54,7 +53,6 @@ pub use self::import::*;
|
|||
#[cfg(all(Py_3_8, not(PyPy)))]
|
||||
pub use self::initconfig::*;
|
||||
pub use self::listobject::*;
|
||||
pub use self::longobject::*;
|
||||
#[cfg(all(Py_3_9, not(PyPy)))]
|
||||
pub use self::methodobject::*;
|
||||
pub use self::object::*;
|
||||
|
|
|
@ -296,8 +296,6 @@ pub struct _specialization_cache {
|
|||
pub getitem: *mut PyObject,
|
||||
#[cfg(Py_3_12)]
|
||||
pub getitem_version: u32,
|
||||
#[cfg(Py_3_13)]
|
||||
pub init: *mut PyObject,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
|
@ -135,9 +135,13 @@ extern "C" {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(PyPy, GraalPy)))]
|
||||
#[cfg(not(GraalPy))]
|
||||
pub unsafe fn Py_CompileString(string: *const c_char, p: *const c_char, s: c_int) -> *mut PyObject {
|
||||
Py_CompileStringExFlags(string, p, s, std::ptr::null_mut(), -1)
|
||||
#[cfg(not(PyPy))]
|
||||
return Py_CompileStringExFlags(string, p, s, std::ptr::null_mut(), -1);
|
||||
|
||||
#[cfg(PyPy)]
|
||||
Py_CompileStringFlags(string, p, s, std::ptr::null_mut())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -449,19 +449,19 @@ pub const PyUnicode_1BYTE_KIND: c_uint = 1;
|
|||
pub const PyUnicode_2BYTE_KIND: c_uint = 2;
|
||||
pub const PyUnicode_4BYTE_KIND: c_uint = 4;
|
||||
|
||||
#[cfg(not(any(GraalPy, PyPy)))]
|
||||
#[cfg(not(GraalPy))]
|
||||
#[inline]
|
||||
pub unsafe fn PyUnicode_1BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS1 {
|
||||
PyUnicode_DATA(op) as *mut Py_UCS1
|
||||
}
|
||||
|
||||
#[cfg(not(any(GraalPy, PyPy)))]
|
||||
#[cfg(not(GraalPy))]
|
||||
#[inline]
|
||||
pub unsafe fn PyUnicode_2BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS2 {
|
||||
PyUnicode_DATA(op) as *mut Py_UCS2
|
||||
}
|
||||
|
||||
#[cfg(not(any(GraalPy, PyPy)))]
|
||||
#[cfg(not(GraalPy))]
|
||||
#[inline]
|
||||
pub unsafe fn PyUnicode_4BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS4 {
|
||||
PyUnicode_DATA(op) as *mut Py_UCS4
|
||||
|
@ -487,7 +487,7 @@ pub unsafe fn _PyUnicode_COMPACT_DATA(op: *mut PyObject) -> *mut c_void {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(GraalPy, PyPy)))]
|
||||
#[cfg(not(GraalPy))]
|
||||
#[inline]
|
||||
pub unsafe fn _PyUnicode_NONCOMPACT_DATA(op: *mut PyObject) -> *mut c_void {
|
||||
debug_assert!(!(*(op as *mut PyUnicodeObject)).data.any.is_null());
|
||||
|
@ -495,7 +495,7 @@ pub unsafe fn _PyUnicode_NONCOMPACT_DATA(op: *mut PyObject) -> *mut c_void {
|
|||
(*(op as *mut PyUnicodeObject)).data.any
|
||||
}
|
||||
|
||||
#[cfg(not(any(GraalPy, PyPy)))]
|
||||
#[cfg(not(GraalPy))]
|
||||
#[inline]
|
||||
pub unsafe fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void {
|
||||
debug_assert!(crate::PyUnicode_Check(op) != 0);
|
||||
|
|
|
@ -13,9 +13,7 @@
|
|||
use crate::{PyLong_AsLong, PyLong_Check, PyObject_GetAttrString, Py_DecRef};
|
||||
use crate::{PyObject, PyObject_TypeCheck, PyTypeObject, Py_TYPE};
|
||||
use std::cell::UnsafeCell;
|
||||
#[cfg(not(GraalPy))]
|
||||
use std::os::raw::c_char;
|
||||
use std::os::raw::c_int;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
use std::ptr;
|
||||
#[cfg(not(PyPy))]
|
||||
use {crate::PyCapsule_Import, std::ffi::CString};
|
||||
|
@ -357,8 +355,8 @@ pub unsafe fn PyDateTime_DELTA_GET_MICROSECONDS(o: *mut PyObject) -> c_int {
|
|||
// but copying them seems suboptimal
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn _get_attr(obj: *mut PyObject, field: &std::ffi::CStr) -> c_int {
|
||||
let result = PyObject_GetAttrString(obj, field.as_ptr());
|
||||
pub unsafe fn _get_attr(obj: *mut PyObject, field: &str) -> c_int {
|
||||
let result = PyObject_GetAttrString(obj, field.as_ptr() as *const c_char);
|
||||
Py_DecRef(result); // the original macros are borrowing
|
||||
if PyLong_Check(result) == 1 {
|
||||
PyLong_AsLong(result) as c_int
|
||||
|
@ -370,55 +368,55 @@ pub unsafe fn _get_attr(obj: *mut PyObject, field: &std::ffi::CStr) -> c_int {
|
|||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_GET_YEAR(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("year"))
|
||||
_get_attr(o, "year\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_GET_MONTH(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("month"))
|
||||
_get_attr(o, "month\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_GET_DAY(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("day"))
|
||||
_get_attr(o, "day\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_DATE_GET_HOUR(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("hour"))
|
||||
_get_attr(o, "hour\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_DATE_GET_MINUTE(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("minute"))
|
||||
_get_attr(o, "minute\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_DATE_GET_SECOND(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("second"))
|
||||
_get_attr(o, "second\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_DATE_GET_MICROSECOND(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("microsecond"))
|
||||
_get_attr(o, "microsecond\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_DATE_GET_FOLD(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("fold"))
|
||||
_get_attr(o, "fold\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_DATE_GET_TZINFO(o: *mut PyObject) -> *mut PyObject {
|
||||
let res = PyObject_GetAttrString(o, c_str!("tzinfo").as_ptr().cast());
|
||||
let res = PyObject_GetAttrString(o, "tzinfo\0".as_ptr() as *const c_char);
|
||||
Py_DecRef(res); // the original macros are borrowing
|
||||
res
|
||||
}
|
||||
|
@ -426,37 +424,37 @@ pub unsafe fn PyDateTime_DATE_GET_TZINFO(o: *mut PyObject) -> *mut PyObject {
|
|||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_TIME_GET_HOUR(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("hour"))
|
||||
_get_attr(o, "hour\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_TIME_GET_MINUTE(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("minute"))
|
||||
_get_attr(o, "minute\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_TIME_GET_SECOND(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("second"))
|
||||
_get_attr(o, "second\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_TIME_GET_MICROSECOND(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("microsecond"))
|
||||
_get_attr(o, "microsecond\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_TIME_GET_FOLD(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("fold"))
|
||||
_get_attr(o, "fold\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_TIME_GET_TZINFO(o: *mut PyObject) -> *mut PyObject {
|
||||
let res = PyObject_GetAttrString(o, c_str!("tzinfo").as_ptr().cast());
|
||||
let res = PyObject_GetAttrString(o, "tzinfo\0".as_ptr() as *const c_char);
|
||||
Py_DecRef(res); // the original macros are borrowing
|
||||
res
|
||||
}
|
||||
|
@ -464,19 +462,19 @@ pub unsafe fn PyDateTime_TIME_GET_TZINFO(o: *mut PyObject) -> *mut PyObject {
|
|||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_DELTA_GET_DAYS(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("days"))
|
||||
_get_attr(o, "days\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_DELTA_GET_SECONDS(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("seconds"))
|
||||
_get_attr(o, "seconds\0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(GraalPy)]
|
||||
pub unsafe fn PyDateTime_DELTA_GET_MICROSECONDS(o: *mut PyObject) -> c_int {
|
||||
_get_attr(o, c_str!("microseconds"))
|
||||
_get_attr(o, "microseconds\0")
|
||||
}
|
||||
|
||||
#[cfg(PyPy)]
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
//!
|
||||
//! PyO3 supports the following software versions:
|
||||
//! - Python 3.7 and up (CPython and PyPy)
|
||||
//! - Rust 1.63 and up
|
||||
//! - Rust 1.56 and up
|
||||
//!
|
||||
//! # Example: Building Python Native modules
|
||||
//!
|
||||
|
@ -88,8 +88,10 @@
|
|||
//!
|
||||
//! static mut MODULE_DEF: PyModuleDef = PyModuleDef {
|
||||
//! m_base: PyModuleDef_HEAD_INIT,
|
||||
//! m_name: c_str!("string_sum").as_ptr(),
|
||||
//! m_doc: c_str!("A Python module written in Rust.").as_ptr(),
|
||||
//! m_name: "string_sum\0".as_ptr().cast::<c_char>(),
|
||||
//! m_doc: "A Python module written in Rust.\0"
|
||||
//! .as_ptr()
|
||||
//! .cast::<c_char>(),
|
||||
//! m_size: 0,
|
||||
//! m_methods: unsafe { METHODS.as_mut_ptr().cast() },
|
||||
//! m_slots: std::ptr::null_mut(),
|
||||
|
@ -100,12 +102,14 @@
|
|||
//!
|
||||
//! static mut METHODS: [PyMethodDef; 2] = [
|
||||
//! PyMethodDef {
|
||||
//! ml_name: c_str!("sum_as_string").as_ptr(),
|
||||
//! ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(),
|
||||
//! ml_meth: PyMethodDefPointer {
|
||||
//! _PyCFunctionFast: sum_as_string,
|
||||
//! },
|
||||
//! ml_flags: METH_FASTCALL,
|
||||
//! ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
|
||||
//! ml_doc: "returns the sum of two integers as a string\0"
|
||||
//! .as_ptr()
|
||||
//! .cast::<c_char>(),
|
||||
//! },
|
||||
//! // A zeroed PyMethodDef to mark the end of the array.
|
||||
//! PyMethodDef::zeroed()
|
||||
|
@ -126,7 +130,9 @@
|
|||
//! if nargs != 2 {
|
||||
//! PyErr_SetString(
|
||||
//! PyExc_TypeError,
|
||||
//! c_str!("sum_as_string() expected 2 positional arguments").as_ptr(),
|
||||
//! "sum_as_string() expected 2 positional arguments\0"
|
||||
//! .as_ptr()
|
||||
//! .cast::<c_char>(),
|
||||
//! );
|
||||
//! return std::ptr::null_mut();
|
||||
//! }
|
||||
|
@ -135,7 +141,9 @@
|
|||
//! if PyLong_Check(arg1) == 0 {
|
||||
//! PyErr_SetString(
|
||||
//! PyExc_TypeError,
|
||||
//! c_str!("sum_as_string() expected an int for positional argument 1").as_ptr(),
|
||||
//! "sum_as_string() expected an int for positional argument 1\0"
|
||||
//! .as_ptr()
|
||||
//! .cast::<c_char>(),
|
||||
//! );
|
||||
//! return std::ptr::null_mut();
|
||||
//! }
|
||||
|
@ -149,7 +157,9 @@
|
|||
//! if PyLong_Check(arg2) == 0 {
|
||||
//! PyErr_SetString(
|
||||
//! PyExc_TypeError,
|
||||
//! c_str!("sum_as_string() expected an int for positional argument 2").as_ptr(),
|
||||
//! "sum_as_string() expected an int for positional argument 2\0"
|
||||
//! .as_ptr()
|
||||
//! .cast::<c_char>(),
|
||||
//! );
|
||||
//! return std::ptr::null_mut();
|
||||
//! }
|
||||
|
@ -167,7 +177,7 @@
|
|||
//! None => {
|
||||
//! PyErr_SetString(
|
||||
//! PyExc_OverflowError,
|
||||
//! c_str!("arguments too large to add").as_ptr(),
|
||||
//! "arguments too large to add\0".as_ptr().cast::<c_char>(),
|
||||
//! );
|
||||
//! std::ptr::null_mut()
|
||||
//! }
|
||||
|
@ -221,10 +231,11 @@
|
|||
//! [`maturin`]: https://github.com/PyO3/maturin "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages"
|
||||
//! [`pyo3-build-config`]: https://docs.rs/pyo3-build-config
|
||||
//! [feature flags]: https://doc.rust-lang.org/cargo/reference/features.html "Features - The Cargo Book"
|
||||
#![doc = concat!("[manual_builds]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution.html#manual-builds \"Manual builds - Building and Distribution - PyO3 user guide\"")]
|
||||
//! [manual_builds]: https://pyo3.rs/latest/building-and-distribution.html#manual-builds "Manual builds - Building and Distribution - PyO3 user guide"
|
||||
//! [setuptools-rust]: https://github.com/PyO3/setuptools-rust "Setuptools plugin for Rust extensions"
|
||||
//! [PEP 384]: https://www.python.org/dev/peps/pep-0384 "PEP 384 -- Defining a Stable ABI"
|
||||
#![doc = concat!("[Features chapter of the guide]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/features.html#features-reference \"Features Reference - PyO3 user guide\"")]
|
||||
//! [Features chapter of the guide]: https://pyo3.rs/latest/features.html#features-reference "Features Reference - PyO3 user guide"
|
||||
|
||||
#![allow(
|
||||
missing_docs,
|
||||
non_camel_case_types,
|
||||
|
@ -245,51 +256,6 @@ macro_rules! opaque_struct {
|
|||
};
|
||||
}
|
||||
|
||||
/// This is a helper macro to create a `&'static CStr`.
|
||||
///
|
||||
/// It can be used on all Rust versions supported by PyO3, unlike c"" literals which
|
||||
/// were stabilised in Rust 1.77.
|
||||
///
|
||||
/// Due to the nature of PyO3 making heavy use of C FFI interop with Python, it is
|
||||
/// common for PyO3 to use CStr.
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::ffi::CStr;
|
||||
///
|
||||
/// const HELLO: &CStr = pyo3_ffi::c_str!("hello");
|
||||
/// static WORLD: &CStr = pyo3_ffi::c_str!("world");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! c_str {
|
||||
($s:expr) => {
|
||||
$crate::_cstr_from_utf8_with_nul_checked(concat!($s, "\0"))
|
||||
};
|
||||
}
|
||||
|
||||
/// Private helper for `c_str!` macro.
|
||||
#[doc(hidden)]
|
||||
pub const fn _cstr_from_utf8_with_nul_checked(s: &str) -> &CStr {
|
||||
// TODO: Replace this implementation with `CStr::from_bytes_with_nul` when MSRV above 1.72.
|
||||
let bytes = s.as_bytes();
|
||||
let len = bytes.len();
|
||||
assert!(
|
||||
!bytes.is_empty() && bytes[bytes.len() - 1] == b'\0',
|
||||
"string is not nul-terminated"
|
||||
);
|
||||
let mut i = 0;
|
||||
let non_null_len = len - 1;
|
||||
while i < non_null_len {
|
||||
assert!(bytes[i] != b'\0', "string contains null bytes");
|
||||
i += 1;
|
||||
}
|
||||
|
||||
unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }
|
||||
}
|
||||
|
||||
use std::ffi::CStr;
|
||||
|
||||
pub use self::abstract_::*;
|
||||
pub use self::bltinmodule::*;
|
||||
pub use self::boolobject::*;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use crate::object::*;
|
||||
use crate::pyport::Py_ssize_t;
|
||||
use libc::size_t;
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
use std::os::raw::c_uchar;
|
||||
use std::os::raw::{c_char, c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void};
|
||||
use std::ptr::addr_of_mut;
|
||||
|
||||
|
@ -88,12 +90,34 @@ extern "C" {
|
|||
arg3: c_int,
|
||||
) -> *mut PyObject;
|
||||
}
|
||||
// skipped non-limited PyLong_FromUnicodeObject
|
||||
// skipped non-limited _PyLong_FromBytes
|
||||
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
extern "C" {
|
||||
// skipped non-limited _PyLong_Sign
|
||||
|
||||
#[cfg_attr(PyPy, link_name = "_PyPyLong_NumBits")]
|
||||
#[cfg(not(Py_3_13))]
|
||||
pub fn _PyLong_NumBits(obj: *mut PyObject) -> size_t;
|
||||
|
||||
// skipped _PyLong_DivmodNear
|
||||
|
||||
#[cfg_attr(PyPy, link_name = "_PyPyLong_FromByteArray")]
|
||||
pub fn _PyLong_FromByteArray(
|
||||
bytes: *const c_uchar,
|
||||
n: size_t,
|
||||
little_endian: c_int,
|
||||
is_signed: c_int,
|
||||
) -> *mut PyObject;
|
||||
|
||||
#[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")]
|
||||
pub fn _PyLong_AsByteArray(
|
||||
v: *mut PyLongObject,
|
||||
bytes: *mut c_uchar,
|
||||
n: size_t,
|
||||
little_endian: c_int,
|
||||
is_signed: c_int,
|
||||
) -> c_int;
|
||||
}
|
||||
|
||||
// skipped non-limited _PyLong_Format
|
||||
|
@ -106,5 +130,6 @@ extern "C" {
|
|||
pub fn PyOS_strtol(arg1: *const c_char, arg2: *mut *mut c_char, arg3: c_int) -> c_long;
|
||||
}
|
||||
|
||||
// skipped non-limited _PyLong_GCD
|
||||
// skipped non-limited _PyLong_Rshift
|
||||
// skipped non-limited _PyLong_Lshift
|
||||
|
|
|
@ -186,8 +186,9 @@ impl std::fmt::Pointer for PyMethodDefPointer {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: This can be a const assert on Rust 1.57
|
||||
const _: () =
|
||||
assert!(mem::size_of::<PyMethodDefPointer>() == mem::size_of::<Option<extern "C" fn()>>());
|
||||
[()][mem::size_of::<PyMethodDefPointer>() - mem::size_of::<Option<extern "C" fn()>>()];
|
||||
|
||||
#[cfg(not(Py_3_9))]
|
||||
extern "C" {
|
||||
|
|
|
@ -261,14 +261,6 @@ extern "C" {
|
|||
#[cfg_attr(PyPy, link_name = "PyPyType_GetQualName")]
|
||||
pub fn PyType_GetQualName(arg1: *mut PyTypeObject) -> *mut PyObject;
|
||||
|
||||
#[cfg(Py_3_13)]
|
||||
#[cfg_attr(PyPy, link_name = "PyPyType_GetFullyQualifiedName")]
|
||||
pub fn PyType_GetFullyQualifiedName(arg1: *mut PyTypeObject) -> *mut PyObject;
|
||||
|
||||
#[cfg(Py_3_13)]
|
||||
#[cfg_attr(PyPy, link_name = "PyPyType_GetModuleName")]
|
||||
pub fn PyType_GetModuleName(arg1: *mut PyTypeObject) -> *mut PyObject;
|
||||
|
||||
#[cfg(Py_3_12)]
|
||||
#[cfg_attr(PyPy, link_name = "PyPyType_FromMetaclass")]
|
||||
pub fn PyType_FromMetaclass(
|
||||
|
|
|
@ -101,7 +101,7 @@ pub unsafe fn PyUnicodeDecodeError_Create(
|
|||
) -> *mut PyObject {
|
||||
crate::_PyObject_CallFunction_SizeT(
|
||||
PyExc_UnicodeDecodeError,
|
||||
c_str!("sy#nns").as_ptr(),
|
||||
b"sy#nns\0".as_ptr().cast::<c_char>(),
|
||||
encoding,
|
||||
object,
|
||||
length,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::object::*;
|
||||
#[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))]
|
||||
use libc::FILE;
|
||||
#[cfg(any(Py_LIMITED_API, not(Py_3_10), PyPy, GraalPy))]
|
||||
#[cfg(all(not(PyPy), any(Py_LIMITED_API, not(Py_3_10), GraalPy)))]
|
||||
use std::os::raw::c_char;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
|
@ -20,28 +20,6 @@ extern "C" {
|
|||
pub fn PyErr_DisplayException(exc: *mut PyObject);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(PyPy)]
|
||||
pub unsafe fn Py_CompileString(string: *const c_char, p: *const c_char, s: c_int) -> *mut PyObject {
|
||||
// PyPy's implementation of Py_CompileString always forwards to Py_CompileStringFlags; this
|
||||
// is only available in the non-limited API and has a real definition for all versions in
|
||||
// the cpython/ subdirectory.
|
||||
#[cfg(Py_LIMITED_API)]
|
||||
extern "C" {
|
||||
#[link_name = "PyPy_CompileStringFlags"]
|
||||
pub fn Py_CompileStringFlags(
|
||||
string: *const c_char,
|
||||
p: *const c_char,
|
||||
s: c_int,
|
||||
f: *mut std::os::raw::c_void, // Actually *mut Py_CompilerFlags in the real definition
|
||||
) -> *mut PyObject;
|
||||
}
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
use crate::Py_CompileStringFlags;
|
||||
|
||||
Py_CompileStringFlags(string, p, s, std::ptr::null_mut())
|
||||
}
|
||||
|
||||
// skipped PyOS_InputHook
|
||||
|
||||
pub const PYOS_STACK_MARGIN: c_int = 2048;
|
||||
|
|
|
@ -328,15 +328,6 @@ extern "C" {
|
|||
pub fn PyUnicode_Compare(left: *mut PyObject, right: *mut PyObject) -> c_int;
|
||||
#[cfg_attr(PyPy, link_name = "PyPyUnicode_CompareWithASCIIString")]
|
||||
pub fn PyUnicode_CompareWithASCIIString(left: *mut PyObject, right: *const c_char) -> c_int;
|
||||
#[cfg(Py_3_13)]
|
||||
pub fn PyUnicode_EqualToUTF8(unicode: *mut PyObject, string: *const c_char) -> c_int;
|
||||
#[cfg(Py_3_13)]
|
||||
pub fn PyUnicode_EqualToUTF8AndSize(
|
||||
unicode: *mut PyObject,
|
||||
string: *const c_char,
|
||||
size: Py_ssize_t,
|
||||
) -> c_int;
|
||||
|
||||
pub fn PyUnicode_RichCompare(
|
||||
left: *mut PyObject,
|
||||
right: *mut PyObject,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.23.0-dev"
|
||||
version = "0.21.2"
|
||||
description = "Code generation for PyO3 package"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||
|
@ -14,22 +14,18 @@ edition = "2021"
|
|||
# not to depend on proc-macro itself.
|
||||
# See https://github.com/PyO3/pyo3/pull/810 for more.
|
||||
[dependencies]
|
||||
heck = "0.5"
|
||||
proc-macro2 = { version = "1.0.60", default-features = false }
|
||||
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.0-dev", features = ["resolve-config"] }
|
||||
heck = "0.4"
|
||||
proc-macro2 = { version = "1", default-features = false }
|
||||
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.21.2", features = ["resolve-config"] }
|
||||
quote = { version = "1", default-features = false }
|
||||
|
||||
[dependencies.syn]
|
||||
version = "2.0.59" # for `LitCStr`
|
||||
version = "2"
|
||||
default-features = false
|
||||
features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"]
|
||||
|
||||
[build-dependencies]
|
||||
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.0-dev" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
experimental-async = []
|
||||
gil-refs = []
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
fn main() {
|
||||
pyo3_build_config::print_expected_cfgs();
|
||||
pyo3_build_config::print_feature_cfgs();
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use syn::{
|
||||
ext::IdentExt,
|
||||
parse::{Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
|
@ -13,23 +12,18 @@ pub mod kw {
|
|||
syn::custom_keyword!(annotation);
|
||||
syn::custom_keyword!(attribute);
|
||||
syn::custom_keyword!(cancel_handle);
|
||||
syn::custom_keyword!(constructor);
|
||||
syn::custom_keyword!(dict);
|
||||
syn::custom_keyword!(eq);
|
||||
syn::custom_keyword!(eq_int);
|
||||
syn::custom_keyword!(extends);
|
||||
syn::custom_keyword!(freelist);
|
||||
syn::custom_keyword!(from_py_with);
|
||||
syn::custom_keyword!(frozen);
|
||||
syn::custom_keyword!(get);
|
||||
syn::custom_keyword!(get_all);
|
||||
syn::custom_keyword!(hash);
|
||||
syn::custom_keyword!(item);
|
||||
syn::custom_keyword!(from_item_all);
|
||||
syn::custom_keyword!(mapping);
|
||||
syn::custom_keyword!(module);
|
||||
syn::custom_keyword!(name);
|
||||
syn::custom_keyword!(ord);
|
||||
syn::custom_keyword!(pass_module);
|
||||
syn::custom_keyword!(rename_all);
|
||||
syn::custom_keyword!(sequence);
|
||||
|
@ -37,7 +31,6 @@ pub mod kw {
|
|||
syn::custom_keyword!(set_all);
|
||||
syn::custom_keyword!(signature);
|
||||
syn::custom_keyword!(subclass);
|
||||
syn::custom_keyword!(submodule);
|
||||
syn::custom_keyword!(text_signature);
|
||||
syn::custom_keyword!(transparent);
|
||||
syn::custom_keyword!(unsendable);
|
||||
|
@ -75,7 +68,7 @@ pub struct NameLitStr(pub Ident);
|
|||
impl Parse for NameLitStr {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let string_literal: LitStr = input.parse()?;
|
||||
if let Ok(ident) = string_literal.parse_with(Ident::parse_any) {
|
||||
if let Ok(ident) = string_literal.parse() {
|
||||
Ok(NameLitStr(ident))
|
||||
} else {
|
||||
bail_spanned!(string_literal.span() => "expected a single identifier in double quotes")
|
||||
|
@ -179,7 +172,6 @@ pub type ModuleAttribute = KeywordAttribute<kw::module, LitStr>;
|
|||
pub type NameAttribute = KeywordAttribute<kw::name, NameLitStr>;
|
||||
pub type RenameAllAttribute = KeywordAttribute<kw::rename_all, RenamingRuleLitStr>;
|
||||
pub type TextSignatureAttribute = KeywordAttribute<kw::text_signature, TextSignatureAttributeValue>;
|
||||
pub type SubmoduleAttribute = kw::submodule;
|
||||
|
||||
impl<K: Parse + std::fmt::Debug, V: Parse> Parse for KeywordAttribute<K, V> {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
|
|
|
@ -1,54 +1,47 @@
|
|||
use crate::method::{FnArg, FnSpec};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote_spanned;
|
||||
use crate::utils::Ctx;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote_spanned, ToTokens};
|
||||
|
||||
pub(crate) fn deprecate_trailing_option_default(spec: &FnSpec<'_>) -> TokenStream {
|
||||
if spec.signature.attribute.is_none()
|
||||
&& spec.tp.signature_attribute_allowed()
|
||||
&& spec.signature.arguments.iter().any(|arg| {
|
||||
if let FnArg::Regular(arg) = arg {
|
||||
arg.option_wrapped_type.is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
{
|
||||
use std::fmt::Write;
|
||||
let mut deprecation_msg = String::from(
|
||||
"this function has implicit defaults for the trailing `Option<T>` arguments \n\
|
||||
= note: these implicit defaults are being phased out \n\
|
||||
= help: add `#[pyo3(signature = (",
|
||||
);
|
||||
spec.signature.arguments.iter().for_each(|arg| {
|
||||
match arg {
|
||||
FnArg::Regular(arg) => {
|
||||
if arg.option_wrapped_type.is_some() {
|
||||
write!(deprecation_msg, "{}=None, ", arg.name)
|
||||
} else {
|
||||
write!(deprecation_msg, "{}, ", arg.name)
|
||||
}
|
||||
}
|
||||
FnArg::VarArgs(arg) => write!(deprecation_msg, "{}, ", arg.name),
|
||||
FnArg::KwArgs(arg) => write!(deprecation_msg, "{}, ", arg.name),
|
||||
FnArg::Py(_) | FnArg::CancelHandle(_) => Ok(()),
|
||||
}
|
||||
.expect("writing to `String` should not fail");
|
||||
});
|
||||
pub enum Deprecation {
|
||||
PyMethodsNewDeprecatedForm,
|
||||
}
|
||||
|
||||
//remove trailing space and comma
|
||||
deprecation_msg.pop();
|
||||
deprecation_msg.pop();
|
||||
|
||||
deprecation_msg.push_str(
|
||||
"))]` to this function to silence this warning and keep the current behavior",
|
||||
);
|
||||
quote_spanned! { spec.name.span() =>
|
||||
#[deprecated(note = #deprecation_msg)]
|
||||
#[allow(dead_code)]
|
||||
const SIGNATURE: () = ();
|
||||
const _: () = SIGNATURE;
|
||||
}
|
||||
} else {
|
||||
TokenStream::new()
|
||||
impl Deprecation {
|
||||
fn ident(&self, span: Span) -> syn::Ident {
|
||||
let string = match self {
|
||||
Deprecation::PyMethodsNewDeprecatedForm => "PYMETHODS_NEW_DEPRECATED_FORM",
|
||||
};
|
||||
syn::Ident::new(string, span)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Deprecations<'ctx>(Vec<(Deprecation, Span)>, &'ctx Ctx);
|
||||
|
||||
impl<'ctx> Deprecations<'ctx> {
|
||||
pub fn new(ctx: &'ctx Ctx) -> Self {
|
||||
Deprecations(Vec::new(), ctx)
|
||||
}
|
||||
|
||||
pub fn push(&mut self, deprecation: Deprecation, span: Span) {
|
||||
self.0.push((deprecation, span))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ToTokens for Deprecations<'ctx> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let Self(deprecations, Ctx { pyo3_path }) = self;
|
||||
|
||||
for (deprecation, span) in deprecations {
|
||||
let pyo3_path = pyo3_path.to_tokens_spanned(*span);
|
||||
let ident = deprecation.ident(*span);
|
||||
quote_spanned!(
|
||||
*span =>
|
||||
#[allow(clippy::let_unit_value)]
|
||||
{
|
||||
let _ = #pyo3_path::impl_::deprecations::#ident;
|
||||
}
|
||||
)
|
||||
.to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::attributes::{self, get_pyo3_options, CrateAttribute, FromPyWithAttribute};
|
||||
use crate::utils::Ctx;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use syn::{
|
||||
parenthesized,
|
||||
parse::{Parse, ParseStream},
|
||||
|
@ -44,14 +44,16 @@ impl<'a> Enum<'a> {
|
|||
}
|
||||
|
||||
/// Build derivation body for enums.
|
||||
fn build(&self, ctx: &Ctx) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
fn build(&self, ctx: &Ctx) -> (TokenStream, TokenStream) {
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let mut var_extracts = Vec::new();
|
||||
let mut variant_names = Vec::new();
|
||||
let mut error_names = Vec::new();
|
||||
|
||||
let mut deprecations = TokenStream::new();
|
||||
for var in &self.variants {
|
||||
let struct_derive = var.build(ctx);
|
||||
let (struct_derive, dep) = var.build(ctx);
|
||||
deprecations.extend(dep);
|
||||
let ext = quote!({
|
||||
let maybe_ret = || -> #pyo3_path::PyResult<Self> {
|
||||
#struct_derive
|
||||
|
@ -68,19 +70,22 @@ impl<'a> Enum<'a> {
|
|||
error_names.push(&var.err_name);
|
||||
}
|
||||
let ty_name = self.enum_ident.to_string();
|
||||
quote!(
|
||||
let errors = [
|
||||
#(#var_extracts),*
|
||||
];
|
||||
::std::result::Result::Err(
|
||||
#pyo3_path::impl_::frompyobject::failed_to_extract_enum(
|
||||
obj.py(),
|
||||
#ty_name,
|
||||
&[#(#variant_names),*],
|
||||
&[#(#error_names),*],
|
||||
&errors
|
||||
(
|
||||
quote!(
|
||||
let errors = [
|
||||
#(#var_extracts),*
|
||||
];
|
||||
::std::result::Result::Err(
|
||||
#pyo3_path::impl_::frompyobject::failed_to_extract_enum(
|
||||
obj.py(),
|
||||
#ty_name,
|
||||
&[#(#variant_names),*],
|
||||
&[#(#error_names),*],
|
||||
&errors
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
deprecations,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -239,7 +244,7 @@ impl<'a> Container<'a> {
|
|||
}
|
||||
|
||||
/// Build derivation body for a struct.
|
||||
fn build(&self, ctx: &Ctx) -> TokenStream {
|
||||
fn build(&self, ctx: &Ctx) -> (TokenStream, TokenStream) {
|
||||
match &self.ty {
|
||||
ContainerType::StructNewtype(ident, from_py_with) => {
|
||||
self.build_newtype_struct(Some(ident), from_py_with, ctx)
|
||||
|
@ -257,43 +262,74 @@ impl<'a> Container<'a> {
|
|||
field_ident: Option<&Ident>,
|
||||
from_py_with: &Option<FromPyWithAttribute>,
|
||||
ctx: &Ctx,
|
||||
) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
) -> (TokenStream, TokenStream) {
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let self_ty = &self.path;
|
||||
let struct_name = self.name();
|
||||
if let Some(ident) = field_ident {
|
||||
let field_name = ident.to_string();
|
||||
match from_py_with {
|
||||
None => quote! {
|
||||
Ok(#self_ty {
|
||||
#ident: #pyo3_path::impl_::frompyobject::extract_struct_field(obj, #struct_name, #field_name)?
|
||||
})
|
||||
},
|
||||
None => (
|
||||
quote! {
|
||||
Ok(#self_ty {
|
||||
#ident: #pyo3_path::impl_::frompyobject::extract_struct_field(obj, #struct_name, #field_name)?
|
||||
})
|
||||
},
|
||||
TokenStream::new(),
|
||||
),
|
||||
Some(FromPyWithAttribute {
|
||||
value: expr_path, ..
|
||||
}) => quote! {
|
||||
Ok(#self_ty {
|
||||
#ident: #pyo3_path::impl_::frompyobject::extract_struct_field_with(#expr_path as fn(_) -> _, obj, #struct_name, #field_name)?
|
||||
})
|
||||
},
|
||||
}) => (
|
||||
quote! {
|
||||
Ok(#self_ty {
|
||||
#ident: #pyo3_path::impl_::frompyobject::extract_struct_field_with(#expr_path as fn(_) -> _, obj, #struct_name, #field_name)?
|
||||
})
|
||||
},
|
||||
quote_spanned! { expr_path.span() =>
|
||||
const _: () = {
|
||||
fn check_from_py_with() {
|
||||
let e = #pyo3_path::impl_::deprecations::GilRefs::new();
|
||||
#pyo3_path::impl_::deprecations::inspect_fn(#expr_path, &e);
|
||||
e.from_py_with_arg();
|
||||
}
|
||||
};
|
||||
},
|
||||
),
|
||||
}
|
||||
} else {
|
||||
match from_py_with {
|
||||
None => quote! {
|
||||
#pyo3_path::impl_::frompyobject::extract_tuple_struct_field(obj, #struct_name, 0).map(#self_ty)
|
||||
},
|
||||
|
||||
None => (
|
||||
quote!(
|
||||
#pyo3_path::impl_::frompyobject::extract_tuple_struct_field(obj, #struct_name, 0).map(#self_ty)
|
||||
),
|
||||
TokenStream::new(),
|
||||
),
|
||||
Some(FromPyWithAttribute {
|
||||
value: expr_path, ..
|
||||
}) => quote! {
|
||||
#pyo3_path::impl_::frompyobject::extract_tuple_struct_field_with(#expr_path as fn(_) -> _, obj, #struct_name, 0).map(#self_ty)
|
||||
},
|
||||
}) => (
|
||||
quote! (
|
||||
#pyo3_path::impl_::frompyobject::extract_tuple_struct_field_with(#expr_path as fn(_) -> _, obj, #struct_name, 0).map(#self_ty)
|
||||
),
|
||||
quote_spanned! { expr_path.span() =>
|
||||
const _: () = {
|
||||
fn check_from_py_with() {
|
||||
let e = #pyo3_path::impl_::deprecations::GilRefs::new();
|
||||
#pyo3_path::impl_::deprecations::inspect_fn(#expr_path, &e);
|
||||
e.from_py_with_arg();
|
||||
}
|
||||
};
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_tuple_struct(&self, struct_fields: &[TupleStructField], ctx: &Ctx) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
fn build_tuple_struct(
|
||||
&self,
|
||||
struct_fields: &[TupleStructField],
|
||||
ctx: &Ctx,
|
||||
) -> (TokenStream, TokenStream) {
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let self_ty = &self.path;
|
||||
let struct_name = &self.name();
|
||||
let field_idents: Vec<_> = (0..struct_fields.len())
|
||||
|
@ -312,16 +348,41 @@ impl<'a> Container<'a> {
|
|||
}
|
||||
});
|
||||
|
||||
quote!(
|
||||
match #pyo3_path::types::PyAnyMethods::extract(obj) {
|
||||
::std::result::Result::Ok((#(#field_idents),*)) => ::std::result::Result::Ok(#self_ty(#(#fields),*)),
|
||||
::std::result::Result::Err(err) => ::std::result::Result::Err(err),
|
||||
}
|
||||
let deprecations = struct_fields
|
||||
.iter()
|
||||
.filter_map(|field| {
|
||||
let FromPyWithAttribute {
|
||||
value: expr_path, ..
|
||||
} = field.from_py_with.as_ref()?;
|
||||
Some(quote_spanned! { expr_path.span() =>
|
||||
const _: () = {
|
||||
fn check_from_py_with() {
|
||||
let e = #pyo3_path::impl_::deprecations::GilRefs::new();
|
||||
#pyo3_path::impl_::deprecations::inspect_fn(#expr_path, &e);
|
||||
e.from_py_with_arg();
|
||||
}
|
||||
};
|
||||
})
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
(
|
||||
quote!(
|
||||
match #pyo3_path::types::PyAnyMethods::extract(obj) {
|
||||
::std::result::Result::Ok((#(#field_idents),*)) => ::std::result::Result::Ok(#self_ty(#(#fields),*)),
|
||||
::std::result::Result::Err(err) => ::std::result::Result::Err(err),
|
||||
}
|
||||
),
|
||||
deprecations,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_struct(&self, struct_fields: &[NamedStructField<'_>], ctx: &Ctx) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
fn build_struct(
|
||||
&self,
|
||||
struct_fields: &[NamedStructField<'_>],
|
||||
ctx: &Ctx,
|
||||
) -> (TokenStream, TokenStream) {
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let self_ty = &self.path;
|
||||
let struct_name = &self.name();
|
||||
let mut fields: Punctuated<TokenStream, syn::Token![,]> = Punctuated::new();
|
||||
|
@ -359,7 +420,28 @@ impl<'a> Container<'a> {
|
|||
fields.push(quote!(#ident: #extractor));
|
||||
}
|
||||
|
||||
quote!(::std::result::Result::Ok(#self_ty{#fields}))
|
||||
let deprecations = struct_fields
|
||||
.iter()
|
||||
.filter_map(|field| {
|
||||
let FromPyWithAttribute {
|
||||
value: expr_path, ..
|
||||
} = field.from_py_with.as_ref()?;
|
||||
Some(quote_spanned! { expr_path.span() =>
|
||||
const _: () = {
|
||||
fn check_from_py_with() {
|
||||
let e = #pyo3_path::impl_::deprecations::GilRefs::new();
|
||||
#pyo3_path::impl_::deprecations::inspect_fn(#expr_path, &e);
|
||||
e.from_py_with_arg();
|
||||
}
|
||||
};
|
||||
})
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
(
|
||||
quote!(::std::result::Result::Ok(#self_ty{#fields})),
|
||||
deprecations,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -588,10 +670,10 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
|
|||
.push(parse_quote!(#gen_ident: FromPyObject<#lt_param>))
|
||||
}
|
||||
let options = ContainerOptions::from_attrs(&tokens.attrs)?;
|
||||
let ctx = &Ctx::new(&options.krate, None);
|
||||
let Ctx { pyo3_path, .. } = &ctx;
|
||||
let ctx = &Ctx::new(&options.krate);
|
||||
let Ctx { pyo3_path } = &ctx;
|
||||
|
||||
let derives = match &tokens.data {
|
||||
let (derives, from_py_with_deprecations) = match &tokens.data {
|
||||
syn::Data::Enum(en) => {
|
||||
if options.transparent || options.annotation.is_some() {
|
||||
bail_spanned!(tokens.span() => "`transparent` or `annotation` is not supported \
|
||||
|
@ -621,5 +703,7 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
|
|||
#derives
|
||||
}
|
||||
}
|
||||
|
||||
#from_py_with_deprecations
|
||||
))
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use std::borrow::Cow;
|
||||
use std::ffi::CString;
|
||||
|
||||
use crate::attributes::{self, get_pyo3_options, take_attributes, NameAttribute};
|
||||
use crate::utils::{Ctx, LitCStr};
|
||||
use proc_macro2::{Ident, Span};
|
||||
use crate::utils::Ctx;
|
||||
use crate::{
|
||||
attributes::{self, get_pyo3_options, take_attributes, NameAttribute},
|
||||
deprecations::Deprecations,
|
||||
};
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
ext::IdentExt,
|
||||
parse::{Parse, ParseStream},
|
||||
|
@ -11,12 +14,12 @@ use syn::{
|
|||
Result,
|
||||
};
|
||||
|
||||
pub struct ConstSpec {
|
||||
pub struct ConstSpec<'ctx> {
|
||||
pub rust_ident: syn::Ident,
|
||||
pub attributes: ConstAttributes,
|
||||
pub attributes: ConstAttributes<'ctx>,
|
||||
}
|
||||
|
||||
impl ConstSpec {
|
||||
impl ConstSpec<'_> {
|
||||
pub fn python_name(&self) -> Cow<'_, Ident> {
|
||||
if let Some(name) = &self.attributes.name {
|
||||
Cow::Borrowed(&name.value.0)
|
||||
|
@ -26,15 +29,16 @@ impl ConstSpec {
|
|||
}
|
||||
|
||||
/// Null-terminated Python name
|
||||
pub fn null_terminated_python_name(&self, ctx: &Ctx) -> LitCStr {
|
||||
let name = self.python_name().to_string();
|
||||
LitCStr::new(CString::new(name).unwrap(), Span::call_site(), ctx)
|
||||
pub fn null_terminated_python_name(&self) -> TokenStream {
|
||||
let name = format!("{}\0", self.python_name());
|
||||
quote!({#name})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConstAttributes {
|
||||
pub struct ConstAttributes<'ctx> {
|
||||
pub is_class_attr: bool,
|
||||
pub name: Option<NameAttribute>,
|
||||
pub deprecations: Deprecations<'ctx>,
|
||||
}
|
||||
|
||||
pub enum PyO3ConstAttribute {
|
||||
|
@ -52,11 +56,12 @@ impl Parse for PyO3ConstAttribute {
|
|||
}
|
||||
}
|
||||
|
||||
impl ConstAttributes {
|
||||
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
|
||||
impl<'ctx> ConstAttributes<'ctx> {
|
||||
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>, ctx: &'ctx Ctx) -> syn::Result<Self> {
|
||||
let mut attributes = ConstAttributes {
|
||||
is_class_attr: false,
|
||||
name: None,
|
||||
deprecations: Deprecations::new(ctx),
|
||||
};
|
||||
|
||||
take_attributes(attrs, |attr| {
|
||||
|
|
|
@ -19,7 +19,6 @@ mod pyclass;
|
|||
mod pyfunction;
|
||||
mod pyimpl;
|
||||
mod pymethod;
|
||||
mod pyversions;
|
||||
mod quotes;
|
||||
|
||||
pub use frompyobject::build_derive_from_pyobject;
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
use std::borrow::Cow;
|
||||
use std::ffi::CString;
|
||||
use std::fmt::Display;
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{format_ident, quote, quote_spanned, ToTokens};
|
||||
use syn::{ext::IdentExt, spanned::Spanned, Ident, Result};
|
||||
|
||||
use crate::deprecations::deprecate_trailing_option_default;
|
||||
use crate::utils::{Ctx, LitCStr};
|
||||
use crate::utils::Ctx;
|
||||
use crate::{
|
||||
attributes::{FromPyWithAttribute, TextSignatureAttribute, TextSignatureAttributeValue},
|
||||
attributes::{TextSignatureAttribute, TextSignatureAttributeValue},
|
||||
deprecations::{Deprecation, Deprecations},
|
||||
params::{impl_arg_params, Holders},
|
||||
pyfunction::{
|
||||
FunctionSignature, PyFunctionArgPyO3Attributes, PyFunctionOptions, SignatureAttribute,
|
||||
|
@ -19,115 +17,19 @@ use crate::{
|
|||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RegularArg<'a> {
|
||||
pub name: Cow<'a, syn::Ident>,
|
||||
pub ty: &'a syn::Type,
|
||||
pub from_py_with: Option<FromPyWithAttribute>,
|
||||
pub default_value: Option<syn::Expr>,
|
||||
pub option_wrapped_type: Option<&'a syn::Type>,
|
||||
}
|
||||
|
||||
/// Pythons *args argument
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VarargsArg<'a> {
|
||||
pub name: Cow<'a, syn::Ident>,
|
||||
pub ty: &'a syn::Type,
|
||||
}
|
||||
|
||||
/// Pythons **kwarg argument
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KwargsArg<'a> {
|
||||
pub name: Cow<'a, syn::Ident>,
|
||||
pub ty: &'a syn::Type,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CancelHandleArg<'a> {
|
||||
pub struct FnArg<'a> {
|
||||
pub name: &'a syn::Ident,
|
||||
pub ty: &'a syn::Type,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PyArg<'a> {
|
||||
pub name: &'a syn::Ident,
|
||||
pub ty: &'a syn::Type,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum FnArg<'a> {
|
||||
Regular(RegularArg<'a>),
|
||||
VarArgs(VarargsArg<'a>),
|
||||
KwArgs(KwargsArg<'a>),
|
||||
Py(PyArg<'a>),
|
||||
CancelHandle(CancelHandleArg<'a>),
|
||||
pub optional: Option<&'a syn::Type>,
|
||||
pub default: Option<syn::Expr>,
|
||||
pub py: bool,
|
||||
pub attrs: PyFunctionArgPyO3Attributes,
|
||||
pub is_varargs: bool,
|
||||
pub is_kwargs: bool,
|
||||
pub is_cancel_handle: bool,
|
||||
}
|
||||
|
||||
impl<'a> FnArg<'a> {
|
||||
pub fn name(&self) -> &syn::Ident {
|
||||
match self {
|
||||
FnArg::Regular(RegularArg { name, .. }) => name,
|
||||
FnArg::VarArgs(VarargsArg { name, .. }) => name,
|
||||
FnArg::KwArgs(KwargsArg { name, .. }) => name,
|
||||
FnArg::Py(PyArg { name, .. }) => name,
|
||||
FnArg::CancelHandle(CancelHandleArg { name, .. }) => name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ty(&self) -> &'a syn::Type {
|
||||
match self {
|
||||
FnArg::Regular(RegularArg { ty, .. }) => ty,
|
||||
FnArg::VarArgs(VarargsArg { ty, .. }) => ty,
|
||||
FnArg::KwArgs(KwargsArg { ty, .. }) => ty,
|
||||
FnArg::Py(PyArg { ty, .. }) => ty,
|
||||
FnArg::CancelHandle(CancelHandleArg { ty, .. }) => ty,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn from_py_with(&self) -> Option<&FromPyWithAttribute> {
|
||||
if let FnArg::Regular(RegularArg { from_py_with, .. }) = self {
|
||||
from_py_with.as_ref()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_varargs_mut(&mut self) -> Result<&mut Self> {
|
||||
if let Self::Regular(RegularArg {
|
||||
name,
|
||||
ty,
|
||||
option_wrapped_type: None,
|
||||
..
|
||||
}) = self
|
||||
{
|
||||
*self = Self::VarArgs(VarargsArg {
|
||||
name: name.clone(),
|
||||
ty,
|
||||
});
|
||||
Ok(self)
|
||||
} else {
|
||||
bail_spanned!(self.name().span() => "args cannot be optional")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_kwargs_mut(&mut self) -> Result<&mut Self> {
|
||||
if let Self::Regular(RegularArg {
|
||||
name,
|
||||
ty,
|
||||
option_wrapped_type: Some(..),
|
||||
..
|
||||
}) = self
|
||||
{
|
||||
*self = Self::KwArgs(KwargsArg {
|
||||
name: name.clone(),
|
||||
ty,
|
||||
});
|
||||
Ok(self)
|
||||
} else {
|
||||
bail_spanned!(self.name().span() => "kwargs must be Option<_>")
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms a rust fn arg parsed with syn into a method::FnArg
|
||||
pub fn parse(arg: &'a mut syn::FnArg) -> Result<Self> {
|
||||
match arg {
|
||||
|
@ -139,43 +41,32 @@ impl<'a> FnArg<'a> {
|
|||
bail_spanned!(cap.ty.span() => IMPL_TRAIT_ERR);
|
||||
}
|
||||
|
||||
let PyFunctionArgPyO3Attributes {
|
||||
from_py_with,
|
||||
cancel_handle,
|
||||
} = PyFunctionArgPyO3Attributes::from_attrs(&mut cap.attrs)?;
|
||||
let arg_attrs = PyFunctionArgPyO3Attributes::from_attrs(&mut cap.attrs)?;
|
||||
let ident = match &*cap.pat {
|
||||
syn::Pat::Ident(syn::PatIdent { ident, .. }) => ident,
|
||||
other => return Err(handle_argument_error(other)),
|
||||
};
|
||||
|
||||
if utils::is_python(&cap.ty) {
|
||||
return Ok(Self::Py(PyArg {
|
||||
name: ident,
|
||||
ty: &cap.ty,
|
||||
}));
|
||||
}
|
||||
let is_cancel_handle = arg_attrs.cancel_handle.is_some();
|
||||
|
||||
if cancel_handle.is_some() {
|
||||
// `PyFunctionArgPyO3Attributes::from_attrs` validates that
|
||||
// only compatible attributes are specified, either
|
||||
// `cancel_handle` or `from_py_with`, dublicates and any
|
||||
// combination of the two are already rejected.
|
||||
return Ok(Self::CancelHandle(CancelHandleArg {
|
||||
name: ident,
|
||||
ty: &cap.ty,
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(Self::Regular(RegularArg {
|
||||
name: Cow::Borrowed(ident),
|
||||
Ok(FnArg {
|
||||
name: ident,
|
||||
ty: &cap.ty,
|
||||
from_py_with,
|
||||
default_value: None,
|
||||
option_wrapped_type: utils::option_type_argument(&cap.ty),
|
||||
}))
|
||||
optional: utils::option_type_argument(&cap.ty),
|
||||
default: None,
|
||||
py: utils::is_python(&cap.ty),
|
||||
attrs: arg_attrs,
|
||||
is_varargs: false,
|
||||
is_kwargs: false,
|
||||
is_cancel_handle,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_regular(&self) -> bool {
|
||||
!self.py && !self.is_cancel_handle && !self.is_kwargs && !self.is_varargs
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_argument_error(pat: &syn::Pat) -> syn::Error {
|
||||
|
@ -191,26 +82,16 @@ fn handle_argument_error(pat: &syn::Pat) -> syn::Error {
|
|||
syn::Error::new(span, msg)
|
||||
}
|
||||
|
||||
/// Represents what kind of a function a pyfunction or pymethod is
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum FnType {
|
||||
/// Represents a pymethod annotated with `#[getter]`
|
||||
Getter(SelfType),
|
||||
/// Represents a pymethod annotated with `#[setter]`
|
||||
Setter(SelfType),
|
||||
/// Represents a regular pymethod
|
||||
Fn(SelfType),
|
||||
/// Represents a pymethod annotated with `#[new]`, i.e. the `__new__` dunder.
|
||||
FnNew,
|
||||
/// Represents a pymethod annotated with both `#[new]` and `#[classmethod]` (in either order)
|
||||
FnNewClass(Span),
|
||||
/// Represents a pymethod annotated with `#[classmethod]`, like a `@classmethod`
|
||||
FnClass(Span),
|
||||
/// Represents a pyfunction or a pymethod annotated with `#[staticmethod]`, like a `@staticmethod`
|
||||
FnStatic,
|
||||
/// Represents a pyfunction annotated with `#[pyo3(pass_module)]
|
||||
FnModule(Span),
|
||||
/// Represents a pymethod or associated constant annotated with `#[classattr]`
|
||||
ClassAttribute,
|
||||
}
|
||||
|
||||
|
@ -227,28 +108,14 @@ impl FnType {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn signature_attribute_allowed(&self) -> bool {
|
||||
match self {
|
||||
FnType::Fn(_)
|
||||
| FnType::FnNew
|
||||
| FnType::FnStatic
|
||||
| FnType::FnClass(_)
|
||||
| FnType::FnNewClass(_)
|
||||
| FnType::FnModule(_) => true,
|
||||
// Setter, Getter and ClassAttribute all have fixed signatures (either take 0 or 1
|
||||
// arguments) so cannot have a `signature = (...)` attribute.
|
||||
FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn self_arg(
|
||||
&self,
|
||||
cls: Option<&syn::Type>,
|
||||
error_mode: ExtractErrorMode,
|
||||
holders: &mut Holders,
|
||||
ctx: &Ctx,
|
||||
) -> Option<TokenStream> {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
) -> TokenStream {
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
match self {
|
||||
FnType::Getter(st) | FnType::Setter(st) | FnType::Fn(st) => {
|
||||
let mut receiver = st.receiver(
|
||||
|
@ -258,35 +125,35 @@ impl FnType {
|
|||
ctx,
|
||||
);
|
||||
syn::Token![,](Span::call_site()).to_tokens(&mut receiver);
|
||||
Some(receiver)
|
||||
receiver
|
||||
}
|
||||
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => {
|
||||
quote!()
|
||||
}
|
||||
FnType::FnClass(span) | FnType::FnNewClass(span) => {
|
||||
let py = syn::Ident::new("py", Span::call_site());
|
||||
let slf: Ident = syn::Ident::new("_slf_ref", Span::call_site());
|
||||
let pyo3_path = pyo3_path.to_tokens_spanned(*span);
|
||||
let ret = quote_spanned! { *span =>
|
||||
quote_spanned! { *span =>
|
||||
#[allow(clippy::useless_conversion)]
|
||||
::std::convert::Into::into(
|
||||
#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &*(#slf as *const _ as *const *mut _))
|
||||
.downcast_unchecked::<#pyo3_path::types::PyType>()
|
||||
),
|
||||
};
|
||||
Some(ret)
|
||||
}
|
||||
}
|
||||
FnType::FnModule(span) => {
|
||||
let py = syn::Ident::new("py", Span::call_site());
|
||||
let slf: Ident = syn::Ident::new("_slf_ref", Span::call_site());
|
||||
let pyo3_path = pyo3_path.to_tokens_spanned(*span);
|
||||
let ret = quote_spanned! { *span =>
|
||||
quote_spanned! { *span =>
|
||||
#[allow(clippy::useless_conversion)]
|
||||
::std::convert::Into::into(
|
||||
#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &*(#slf as *const _ as *const *mut _))
|
||||
.downcast_unchecked::<#pyo3_path::types::PyModule>()
|
||||
),
|
||||
};
|
||||
Some(ret)
|
||||
}
|
||||
}
|
||||
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -305,7 +172,7 @@ pub enum ExtractErrorMode {
|
|||
|
||||
impl ExtractErrorMode {
|
||||
pub fn handle_error(self, extract: TokenStream, ctx: &Ctx) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
match self {
|
||||
ExtractErrorMode::Raise => quote! { #extract? },
|
||||
ExtractErrorMode::NotImplemented => quote! {
|
||||
|
@ -330,7 +197,7 @@ impl SelfType {
|
|||
// main macro callsite.
|
||||
let py = syn::Ident::new("py", Span::call_site());
|
||||
let slf = syn::Ident::new("_slf", Span::call_site());
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
match self {
|
||||
SelfType::Receiver { span, mutable } => {
|
||||
let method = if *mutable {
|
||||
|
@ -410,6 +277,7 @@ pub struct FnSpec<'a> {
|
|||
pub text_signature: Option<TextSignatureAttribute>,
|
||||
pub asyncness: Option<syn::Token![async]>,
|
||||
pub unsafety: Option<syn::Token![unsafe]>,
|
||||
pub deprecations: Deprecations<'a>,
|
||||
}
|
||||
|
||||
pub fn parse_method_receiver(arg: &syn::FnArg) -> Result<SelfType> {
|
||||
|
@ -441,6 +309,7 @@ impl<'a> FnSpec<'a> {
|
|||
sig: &'a mut syn::Signature,
|
||||
meth_attrs: &mut Vec<syn::Attribute>,
|
||||
options: PyFunctionOptions,
|
||||
ctx: &'a Ctx,
|
||||
) -> Result<FnSpec<'a>> {
|
||||
let PyFunctionOptions {
|
||||
text_signature,
|
||||
|
@ -450,8 +319,9 @@ impl<'a> FnSpec<'a> {
|
|||
} = options;
|
||||
|
||||
let mut python_name = name.map(|name| name.value.0);
|
||||
let mut deprecations = Deprecations::new(ctx);
|
||||
|
||||
let fn_type = Self::parse_fn_type(sig, meth_attrs, &mut python_name)?;
|
||||
let fn_type = Self::parse_fn_type(sig, meth_attrs, &mut python_name, &mut deprecations)?;
|
||||
ensure_signatures_on_valid_method(&fn_type, signature.as_ref(), text_signature.as_ref())?;
|
||||
|
||||
let name = &sig.ident;
|
||||
|
@ -489,21 +359,21 @@ impl<'a> FnSpec<'a> {
|
|||
text_signature,
|
||||
asyncness: sig.asyncness,
|
||||
unsafety: sig.unsafety,
|
||||
deprecations,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn null_terminated_python_name(&self, ctx: &Ctx) -> LitCStr {
|
||||
let name = self.python_name.to_string();
|
||||
let name = CString::new(name).unwrap();
|
||||
LitCStr::new(name, self.python_name.span(), ctx)
|
||||
pub fn null_terminated_python_name(&self) -> syn::LitStr {
|
||||
syn::LitStr::new(&format!("{}\0", self.python_name), self.python_name.span())
|
||||
}
|
||||
|
||||
fn parse_fn_type(
|
||||
sig: &syn::Signature,
|
||||
meth_attrs: &mut Vec<syn::Attribute>,
|
||||
python_name: &mut Option<syn::Ident>,
|
||||
deprecations: &mut Deprecations<'_>,
|
||||
) -> Result<FnType> {
|
||||
let mut method_attributes = parse_method_attributes(meth_attrs)?;
|
||||
let mut method_attributes = parse_method_attributes(meth_attrs, deprecations)?;
|
||||
|
||||
let name = &sig.ident;
|
||||
let parse_receiver = |msg: &'static str| {
|
||||
|
@ -617,22 +487,17 @@ impl<'a> FnSpec<'a> {
|
|||
cls: Option<&syn::Type>,
|
||||
ctx: &Ctx,
|
||||
) -> Result<TokenStream> {
|
||||
let Ctx {
|
||||
pyo3_path,
|
||||
output_span,
|
||||
} = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let mut cancel_handle_iter = self
|
||||
.signature
|
||||
.arguments
|
||||
.iter()
|
||||
.filter(|arg| matches!(arg, FnArg::CancelHandle(..)));
|
||||
.filter(|arg| arg.is_cancel_handle);
|
||||
let cancel_handle = cancel_handle_iter.next();
|
||||
if let Some(FnArg::CancelHandle(CancelHandleArg { name, .. })) = cancel_handle {
|
||||
ensure_spanned!(self.asyncness.is_some(), name.span() => "`cancel_handle` attribute can only be used with `async fn`");
|
||||
if let Some(FnArg::CancelHandle(CancelHandleArg { name, .. })) =
|
||||
cancel_handle_iter.next()
|
||||
{
|
||||
bail_spanned!(name.span() => "`cancel_handle` may only be specified once");
|
||||
if let Some(arg) = cancel_handle {
|
||||
ensure_spanned!(self.asyncness.is_some(), arg.name.span() => "`cancel_handle` attribute can only be used with `async fn`");
|
||||
if let Some(arg2) = cancel_handle_iter.next() {
|
||||
bail_spanned!(arg2.name.span() => "`cancel_handle` may only be specified once");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -676,16 +541,18 @@ impl<'a> FnSpec<'a> {
|
|||
}}
|
||||
}
|
||||
_ => {
|
||||
if let Some(self_arg) = self_arg() {
|
||||
let self_arg = self_arg();
|
||||
if self_arg.is_empty() {
|
||||
quote! { function(#(#args),*) }
|
||||
} else {
|
||||
let self_checker = holders.push_gil_refs_checker(self_arg.span());
|
||||
quote! {
|
||||
function(
|
||||
// NB #self_arg includes a comma, so none inserted here
|
||||
#self_arg
|
||||
#pyo3_path::impl_::deprecations::inspect_type(#self_arg &#self_checker),
|
||||
#(#args),*
|
||||
)
|
||||
}
|
||||
} else {
|
||||
quote! { function(#(#args),*) }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -706,29 +573,22 @@ impl<'a> FnSpec<'a> {
|
|||
}};
|
||||
}
|
||||
call
|
||||
} else if let Some(self_arg) = self_arg() {
|
||||
quote! {
|
||||
function(
|
||||
// NB #self_arg includes a comma, so none inserted here
|
||||
#self_arg
|
||||
#(#args),*
|
||||
)
|
||||
}
|
||||
} else {
|
||||
quote! { function(#(#args),*) }
|
||||
};
|
||||
|
||||
// We must assign the output_span to the return value of the call,
|
||||
// but *not* of the call itself otherwise the spans get really weird
|
||||
let ret_expr = quote! { let ret = #call; };
|
||||
let ret_var = quote_spanned! {*output_span=> ret };
|
||||
let return_conversion = quotes::map_result_into_ptr(quotes::ok_wrap(ret_var, ctx), ctx);
|
||||
quote! {
|
||||
{
|
||||
#ret_expr
|
||||
#return_conversion
|
||||
let self_arg = self_arg();
|
||||
if self_arg.is_empty() {
|
||||
quote! { function(#(#args),*) }
|
||||
} else {
|
||||
let self_checker = holders.push_gil_refs_checker(self_arg.span());
|
||||
quote! {
|
||||
function(
|
||||
// NB #self_arg includes a comma, so none inserted here
|
||||
#pyo3_path::impl_::deprecations::inspect_type(#self_arg &#self_checker),
|
||||
#(#args),*
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
quotes::map_result_into_ptr(quotes::ok_wrap(call, ctx), ctx)
|
||||
};
|
||||
|
||||
let func_name = &self.name;
|
||||
|
@ -738,8 +598,6 @@ impl<'a> FnSpec<'a> {
|
|||
quote!(#func_name)
|
||||
};
|
||||
|
||||
let deprecation = deprecate_trailing_option_default(self);
|
||||
|
||||
Ok(match self.convention {
|
||||
CallingConvention::Noargs => {
|
||||
let mut holders = Holders::new();
|
||||
|
@ -747,33 +605,40 @@ impl<'a> FnSpec<'a> {
|
|||
.signature
|
||||
.arguments
|
||||
.iter()
|
||||
.map(|arg| match arg {
|
||||
FnArg::Py(..) => quote!(py),
|
||||
FnArg::CancelHandle(..) => quote!(__cancel_handle),
|
||||
_ => unreachable!("`CallingConvention::Noargs` should not contain any arguments (reaching Python) except for `self`, which is handled below."),
|
||||
.map(|arg| {
|
||||
if arg.py {
|
||||
quote!(py)
|
||||
} else if arg.is_cancel_handle {
|
||||
quote!(__cancel_handle)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let call = rust_call(args, &mut holders);
|
||||
let check_gil_refs = holders.check_gil_refs();
|
||||
let init_holders = holders.init_holders(ctx);
|
||||
|
||||
quote! {
|
||||
unsafe fn #ident<'py>(
|
||||
py: #pyo3_path::Python<'py>,
|
||||
_slf: *mut #pyo3_path::ffi::PyObject,
|
||||
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
|
||||
#deprecation
|
||||
let _slf_ref = &_slf;
|
||||
let function = #rust_name; // Shadow the function name to avoid #3017
|
||||
#init_holders
|
||||
let result = #call;
|
||||
#check_gil_refs
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
CallingConvention::Fastcall => {
|
||||
let mut holders = Holders::new();
|
||||
let (arg_convert, args) = impl_arg_params(self, cls, true, &mut holders, ctx);
|
||||
let (arg_convert, args) = impl_arg_params(self, cls, true, &mut holders, ctx)?;
|
||||
let call = rust_call(args, &mut holders);
|
||||
let init_holders = holders.init_holders(ctx);
|
||||
let check_gil_refs = holders.check_gil_refs();
|
||||
|
||||
quote! {
|
||||
unsafe fn #ident<'py>(
|
||||
|
@ -783,21 +648,22 @@ impl<'a> FnSpec<'a> {
|
|||
_nargs: #pyo3_path::ffi::Py_ssize_t,
|
||||
_kwnames: *mut #pyo3_path::ffi::PyObject
|
||||
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
|
||||
#deprecation
|
||||
let _slf_ref = &_slf;
|
||||
let function = #rust_name; // Shadow the function name to avoid #3017
|
||||
#arg_convert
|
||||
#init_holders
|
||||
let result = #call;
|
||||
#check_gil_refs
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
CallingConvention::Varargs => {
|
||||
let mut holders = Holders::new();
|
||||
let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx);
|
||||
let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx)?;
|
||||
let call = rust_call(args, &mut holders);
|
||||
let init_holders = holders.init_holders(ctx);
|
||||
let check_gil_refs = holders.check_gil_refs();
|
||||
|
||||
quote! {
|
||||
unsafe fn #ident<'py>(
|
||||
|
@ -806,24 +672,25 @@ impl<'a> FnSpec<'a> {
|
|||
_args: *mut #pyo3_path::ffi::PyObject,
|
||||
_kwargs: *mut #pyo3_path::ffi::PyObject
|
||||
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
|
||||
#deprecation
|
||||
let _slf_ref = &_slf;
|
||||
let function = #rust_name; // Shadow the function name to avoid #3017
|
||||
#arg_convert
|
||||
#init_holders
|
||||
let result = #call;
|
||||
#check_gil_refs
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
CallingConvention::TpNew => {
|
||||
let mut holders = Holders::new();
|
||||
let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx);
|
||||
let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx)?;
|
||||
let self_arg = self
|
||||
.tp
|
||||
.self_arg(cls, ExtractErrorMode::Raise, &mut holders, ctx);
|
||||
let call = quote_spanned! {*output_span=> #rust_name(#self_arg #(#args),*) };
|
||||
let call = quote! { #rust_name(#self_arg #(#args),*) };
|
||||
let init_holders = holders.init_holders(ctx);
|
||||
let check_gil_refs = holders.check_gil_refs();
|
||||
quote! {
|
||||
unsafe fn #ident(
|
||||
py: #pyo3_path::Python<'_>,
|
||||
|
@ -832,13 +699,13 @@ impl<'a> FnSpec<'a> {
|
|||
_kwargs: *mut #pyo3_path::ffi::PyObject
|
||||
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
|
||||
use #pyo3_path::callback::IntoPyCallbackOutput;
|
||||
#deprecation
|
||||
let _slf_ref = &_slf;
|
||||
let function = #rust_name; // Shadow the function name to avoid #3017
|
||||
#arg_convert
|
||||
#init_holders
|
||||
let result = #call;
|
||||
let initializer: #pyo3_path::PyClassInitializer::<#cls> = result.convert(py)?;
|
||||
#check_gil_refs
|
||||
#pyo3_path::impl_::pymethods::tp_new_impl(py, initializer, _slf)
|
||||
}
|
||||
}
|
||||
|
@ -849,13 +716,13 @@ impl<'a> FnSpec<'a> {
|
|||
/// Return a `PyMethodDef` constructor for this function, matching the selected
|
||||
/// calling convention.
|
||||
pub fn get_methoddef(&self, wrapper: impl ToTokens, doc: &PythonDoc, ctx: &Ctx) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let python_name = self.null_terminated_python_name(ctx);
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let python_name = self.null_terminated_python_name();
|
||||
match self.convention {
|
||||
CallingConvention::Noargs => quote! {
|
||||
#pyo3_path::impl_::pymethods::PyMethodDef::noargs(
|
||||
#python_name,
|
||||
{
|
||||
#pyo3_path::impl_::pymethods::PyCFunction({
|
||||
unsafe extern "C" fn trampoline(
|
||||
_slf: *mut #pyo3_path::ffi::PyObject,
|
||||
_args: *mut #pyo3_path::ffi::PyObject,
|
||||
|
@ -868,14 +735,14 @@ impl<'a> FnSpec<'a> {
|
|||
)
|
||||
}
|
||||
trampoline
|
||||
},
|
||||
}),
|
||||
#doc,
|
||||
)
|
||||
},
|
||||
CallingConvention::Fastcall => quote! {
|
||||
#pyo3_path::impl_::pymethods::PyMethodDef::fastcall_cfunction_with_keywords(
|
||||
#python_name,
|
||||
{
|
||||
#pyo3_path::impl_::pymethods::PyCFunctionFastWithKeywords({
|
||||
unsafe extern "C" fn trampoline(
|
||||
_slf: *mut #pyo3_path::ffi::PyObject,
|
||||
_args: *const *mut #pyo3_path::ffi::PyObject,
|
||||
|
@ -892,14 +759,14 @@ impl<'a> FnSpec<'a> {
|
|||
)
|
||||
}
|
||||
trampoline
|
||||
},
|
||||
}),
|
||||
#doc,
|
||||
)
|
||||
},
|
||||
CallingConvention::Varargs => quote! {
|
||||
#pyo3_path::impl_::pymethods::PyMethodDef::cfunction_with_keywords(
|
||||
#python_name,
|
||||
{
|
||||
#pyo3_path::impl_::pymethods::PyCFunctionWithKeywords({
|
||||
unsafe extern "C" fn trampoline(
|
||||
_slf: *mut #pyo3_path::ffi::PyObject,
|
||||
_args: *mut #pyo3_path::ffi::PyObject,
|
||||
|
@ -914,7 +781,7 @@ impl<'a> FnSpec<'a> {
|
|||
)
|
||||
}
|
||||
trampoline
|
||||
},
|
||||
}),
|
||||
#doc,
|
||||
)
|
||||
},
|
||||
|
@ -923,11 +790,11 @@ impl<'a> FnSpec<'a> {
|
|||
}
|
||||
|
||||
/// Forwards to [utils::get_doc] with the text signature of this spec.
|
||||
pub fn get_doc(&self, attrs: &[syn::Attribute], ctx: &Ctx) -> PythonDoc {
|
||||
pub fn get_doc(&self, attrs: &[syn::Attribute]) -> PythonDoc {
|
||||
let text_signature = self
|
||||
.text_signature_call_signature()
|
||||
.map(|sig| format!("{}{}", self.python_name, sig));
|
||||
utils::get_doc(attrs, text_signature, ctx)
|
||||
utils::get_doc(attrs, text_signature)
|
||||
}
|
||||
|
||||
/// Creates the parenthesised arguments list for `__text_signature__` snippet based on this spec's signature
|
||||
|
@ -976,7 +843,10 @@ impl MethodTypeAttribute {
|
|||
/// If the attribute does not match one of the attribute names, returns `Ok(None)`.
|
||||
///
|
||||
/// Otherwise will either return a parse error or the attribute.
|
||||
fn parse_if_matching_attribute(attr: &syn::Attribute) -> Result<Option<Self>> {
|
||||
fn parse_if_matching_attribute(
|
||||
attr: &syn::Attribute,
|
||||
deprecations: &mut Deprecations<'_>,
|
||||
) -> Result<Option<Self>> {
|
||||
fn ensure_no_arguments(meta: &syn::Meta, ident: &str) -> syn::Result<()> {
|
||||
match meta {
|
||||
syn::Meta::Path(_) => Ok(()),
|
||||
|
@ -1020,6 +890,11 @@ impl MethodTypeAttribute {
|
|||
if path.is_ident("new") {
|
||||
ensure_no_arguments(meta, "new")?;
|
||||
Ok(Some(MethodTypeAttribute::New(path.span())))
|
||||
} else if path.is_ident("__new__") {
|
||||
let span = path.span();
|
||||
deprecations.push(Deprecation::PyMethodsNewDeprecatedForm, span);
|
||||
ensure_no_arguments(meta, "__new__")?;
|
||||
Ok(Some(MethodTypeAttribute::New(span)))
|
||||
} else if path.is_ident("classmethod") {
|
||||
ensure_no_arguments(meta, "classmethod")?;
|
||||
Ok(Some(MethodTypeAttribute::ClassMethod(path.span())))
|
||||
|
@ -1054,12 +929,15 @@ impl Display for MethodTypeAttribute {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_method_attributes(attrs: &mut Vec<syn::Attribute>) -> Result<Vec<MethodTypeAttribute>> {
|
||||
fn parse_method_attributes(
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
deprecations: &mut Deprecations<'_>,
|
||||
) -> Result<Vec<MethodTypeAttribute>> {
|
||||
let mut new_attrs = Vec::new();
|
||||
let mut found_attrs = Vec::new();
|
||||
|
||||
for attr in attrs.drain(..) {
|
||||
match MethodTypeAttribute::parse_if_matching_attribute(&attr)? {
|
||||
match MethodTypeAttribute::parse_if_matching_attribute(&attr, deprecations)? {
|
||||
Some(attr) => found_attrs.push(attr),
|
||||
None => new_attrs.push(attr),
|
||||
}
|
||||
|
@ -1083,18 +961,15 @@ fn ensure_signatures_on_valid_method(
|
|||
if let Some(signature) = signature {
|
||||
match fn_type {
|
||||
FnType::Getter(_) => {
|
||||
debug_assert!(!fn_type.signature_attribute_allowed());
|
||||
bail_spanned!(signature.kw.span() => "`signature` not allowed with `getter`")
|
||||
}
|
||||
FnType::Setter(_) => {
|
||||
debug_assert!(!fn_type.signature_attribute_allowed());
|
||||
bail_spanned!(signature.kw.span() => "`signature` not allowed with `setter`")
|
||||
}
|
||||
FnType::ClassAttribute => {
|
||||
debug_assert!(!fn_type.signature_attribute_allowed());
|
||||
bail_spanned!(signature.kw.span() => "`signature` not allowed with `classattr`")
|
||||
}
|
||||
_ => debug_assert!(fn_type.signature_attribute_allowed()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(text_signature) = text_signature {
|
||||
|
|
|
@ -1,113 +1,82 @@
|
|||
//! Code generation for the function that initializes a python module and adds classes and function.
|
||||
|
||||
use crate::utils::Ctx;
|
||||
use crate::{
|
||||
attributes::{
|
||||
self, kw, take_attributes, take_pyo3_options, CrateAttribute, ModuleAttribute,
|
||||
NameAttribute, SubmoduleAttribute,
|
||||
},
|
||||
attributes::{self, take_attributes, take_pyo3_options, CrateAttribute, NameAttribute},
|
||||
get_doc,
|
||||
pyclass::PyClassPyO3Option,
|
||||
pyfunction::{impl_wrap_pyfunction, PyFunctionOptions},
|
||||
utils::{Ctx, LitCStr, PyO3CratePath},
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use std::ffi::CString;
|
||||
use syn::{
|
||||
ext::IdentExt,
|
||||
parse::{Parse, ParseStream},
|
||||
parse_quote, parse_quote_spanned,
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
token::Comma,
|
||||
Item, Meta, Path, Result,
|
||||
Item, Path, Result,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PyModuleOptions {
|
||||
krate: Option<CrateAttribute>,
|
||||
name: Option<NameAttribute>,
|
||||
module: Option<ModuleAttribute>,
|
||||
submodule: Option<kw::submodule>,
|
||||
}
|
||||
|
||||
impl Parse for PyModuleOptions {
|
||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||
let mut options: PyModuleOptions = Default::default();
|
||||
|
||||
options.add_attributes(
|
||||
Punctuated::<PyModulePyO3Option, syn::Token![,]>::parse_terminated(input)?,
|
||||
)?;
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
name: Option<syn::Ident>,
|
||||
}
|
||||
|
||||
impl PyModuleOptions {
|
||||
fn take_pyo3_options(&mut self, attrs: &mut Vec<syn::Attribute>) -> Result<()> {
|
||||
self.add_attributes(take_pyo3_options(attrs)?)
|
||||
}
|
||||
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
|
||||
let mut options: PyModuleOptions = Default::default();
|
||||
|
||||
fn add_attributes(
|
||||
&mut self,
|
||||
attrs: impl IntoIterator<Item = PyModulePyO3Option>,
|
||||
) -> Result<()> {
|
||||
macro_rules! set_option {
|
||||
($key:ident $(, $extra:literal)?) => {
|
||||
{
|
||||
ensure_spanned!(
|
||||
self.$key.is_none(),
|
||||
$key.span() => concat!("`", stringify!($key), "` may only be specified once" $(, $extra)?)
|
||||
);
|
||||
self.$key = Some($key);
|
||||
}
|
||||
};
|
||||
}
|
||||
for attr in attrs {
|
||||
match attr {
|
||||
PyModulePyO3Option::Crate(krate) => set_option!(krate),
|
||||
PyModulePyO3Option::Name(name) => set_option!(name),
|
||||
PyModulePyO3Option::Module(module) => set_option!(module),
|
||||
PyModulePyO3Option::Submodule(submodule) => set_option!(
|
||||
submodule,
|
||||
" (it is implicitly always specified for nested modules)"
|
||||
),
|
||||
for option in take_pyo3_options(attrs)? {
|
||||
match option {
|
||||
PyModulePyO3Option::Name(name) => options.set_name(name.value.0)?,
|
||||
PyModulePyO3Option::Crate(path) => options.set_crate(path)?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
fn set_name(&mut self, name: syn::Ident) -> Result<()> {
|
||||
ensure_spanned!(
|
||||
self.name.is_none(),
|
||||
name.span() => "`name` may only be specified once"
|
||||
);
|
||||
|
||||
self.name = Some(name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_crate(&mut self, path: CrateAttribute) -> Result<()> {
|
||||
ensure_spanned!(
|
||||
self.krate.is_none(),
|
||||
path.span() => "`crate` may only be specified once"
|
||||
);
|
||||
|
||||
self.krate = Some(path);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pymodule_module_impl(
|
||||
module: &mut syn::ItemMod,
|
||||
mut options: PyModuleOptions,
|
||||
) -> Result<TokenStream> {
|
||||
pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
|
||||
let syn::ItemMod {
|
||||
attrs,
|
||||
vis,
|
||||
unsafety: _,
|
||||
ident,
|
||||
mod_token,
|
||||
mod_token: _,
|
||||
content,
|
||||
semi: _,
|
||||
} = module;
|
||||
} = &mut module;
|
||||
let items = if let Some((_, items)) = content {
|
||||
items
|
||||
} else {
|
||||
bail_spanned!(mod_token.span() => "`#[pymodule]` can only be used on inline modules")
|
||||
};
|
||||
options.take_pyo3_options(attrs)?;
|
||||
let ctx = &Ctx::new(&options.krate, None);
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let doc = get_doc(attrs, None, ctx);
|
||||
let name = options
|
||||
.name
|
||||
.map_or_else(|| ident.unraw(), |name| name.value.0);
|
||||
let full_name = if let Some(module) = &options.module {
|
||||
format!("{}.{}", module.value.value(), name)
|
||||
} else {
|
||||
name.to_string()
|
||||
bail_spanned!(module.span() => "`#[pymodule]` can only be used on inline modules")
|
||||
};
|
||||
let options = PyModuleOptions::from_attrs(attrs)?;
|
||||
let ctx = &Ctx::new(&options.krate);
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let doc = get_doc(attrs, None);
|
||||
|
||||
let mut module_items = Vec::new();
|
||||
let mut module_items_cfg_attrs = Vec::new();
|
||||
|
@ -174,18 +143,7 @@ pub fn pymodule_module_impl(
|
|||
);
|
||||
ensure_spanned!(pymodule_init.is_none(), item_fn.span() => "only one `#[pymodule_init]` may be specified");
|
||||
pymodule_init = Some(quote! { #ident(module)?; });
|
||||
} else if has_attribute(&item_fn.attrs, "pyfunction")
|
||||
|| has_attribute_with_namespace(
|
||||
&item_fn.attrs,
|
||||
Some(pyo3_path),
|
||||
&["pyfunction"],
|
||||
)
|
||||
|| has_attribute_with_namespace(
|
||||
&item_fn.attrs,
|
||||
Some(pyo3_path),
|
||||
&["prelude", "pyfunction"],
|
||||
)
|
||||
{
|
||||
} else if has_attribute(&item_fn.attrs, "pyfunction") {
|
||||
module_items.push(ident.clone());
|
||||
module_items_cfg_attrs.push(get_cfg_attributes(&item_fn.attrs));
|
||||
}
|
||||
|
@ -195,27 +153,9 @@ pub fn pymodule_module_impl(
|
|||
!has_attribute(&item_struct.attrs, "pymodule_export"),
|
||||
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||
);
|
||||
if has_attribute(&item_struct.attrs, "pyclass")
|
||||
|| has_attribute_with_namespace(
|
||||
&item_struct.attrs,
|
||||
Some(pyo3_path),
|
||||
&["pyclass"],
|
||||
)
|
||||
|| has_attribute_with_namespace(
|
||||
&item_struct.attrs,
|
||||
Some(pyo3_path),
|
||||
&["prelude", "pyclass"],
|
||||
)
|
||||
{
|
||||
if has_attribute(&item_struct.attrs, "pyclass") {
|
||||
module_items.push(item_struct.ident.clone());
|
||||
module_items_cfg_attrs.push(get_cfg_attributes(&item_struct.attrs));
|
||||
if !has_pyo3_module_declared::<PyClassPyO3Option>(
|
||||
&item_struct.attrs,
|
||||
"pyclass",
|
||||
|option| matches!(option, PyClassPyO3Option::Module(_)),
|
||||
)? {
|
||||
set_module_attribute(&mut item_struct.attrs, &full_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
Item::Enum(item_enum) => {
|
||||
|
@ -223,23 +163,9 @@ pub fn pymodule_module_impl(
|
|||
!has_attribute(&item_enum.attrs, "pymodule_export"),
|
||||
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||
);
|
||||
if has_attribute(&item_enum.attrs, "pyclass")
|
||||
|| has_attribute_with_namespace(&item_enum.attrs, Some(pyo3_path), &["pyclass"])
|
||||
|| has_attribute_with_namespace(
|
||||
&item_enum.attrs,
|
||||
Some(pyo3_path),
|
||||
&["prelude", "pyclass"],
|
||||
)
|
||||
{
|
||||
if has_attribute(&item_enum.attrs, "pyclass") {
|
||||
module_items.push(item_enum.ident.clone());
|
||||
module_items_cfg_attrs.push(get_cfg_attributes(&item_enum.attrs));
|
||||
if !has_pyo3_module_declared::<PyClassPyO3Option>(
|
||||
&item_enum.attrs,
|
||||
"pyclass",
|
||||
|option| matches!(option, PyClassPyO3Option::Module(_)),
|
||||
)? {
|
||||
set_module_attribute(&mut item_enum.attrs, &full_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
Item::Mod(item_mod) => {
|
||||
|
@ -247,26 +173,9 @@ pub fn pymodule_module_impl(
|
|||
!has_attribute(&item_mod.attrs, "pymodule_export"),
|
||||
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||
);
|
||||
if has_attribute(&item_mod.attrs, "pymodule")
|
||||
|| has_attribute_with_namespace(&item_mod.attrs, Some(pyo3_path), &["pymodule"])
|
||||
|| has_attribute_with_namespace(
|
||||
&item_mod.attrs,
|
||||
Some(pyo3_path),
|
||||
&["prelude", "pymodule"],
|
||||
)
|
||||
{
|
||||
if has_attribute(&item_mod.attrs, "pymodule") {
|
||||
module_items.push(item_mod.ident.clone());
|
||||
module_items_cfg_attrs.push(get_cfg_attributes(&item_mod.attrs));
|
||||
if !has_pyo3_module_declared::<PyModulePyO3Option>(
|
||||
&item_mod.attrs,
|
||||
"pymodule",
|
||||
|option| matches!(option, PyModulePyO3Option::Module(_)),
|
||||
)? {
|
||||
set_module_attribute(&mut item_mod.attrs, &full_name);
|
||||
}
|
||||
item_mod
|
||||
.attrs
|
||||
.push(parse_quote_spanned!(item_mod.mod_token.span()=> #[pyo3(submodule)]));
|
||||
}
|
||||
}
|
||||
Item::ForeignMod(item) => {
|
||||
|
@ -333,26 +242,28 @@ pub fn pymodule_module_impl(
|
|||
}
|
||||
}
|
||||
|
||||
let module_def = quote! {{
|
||||
use #pyo3_path::impl_::pymodule as impl_;
|
||||
const INITIALIZER: impl_::ModuleInitializer = impl_::ModuleInitializer(__pyo3_pymodule);
|
||||
unsafe {
|
||||
impl_::ModuleDef::new(
|
||||
__PYO3_NAME,
|
||||
#doc,
|
||||
INITIALIZER
|
||||
)
|
||||
}
|
||||
}};
|
||||
let initialization = module_initialization(&name, ctx, module_def, options.submodule.is_some());
|
||||
|
||||
let initialization = module_initialization(options, ident);
|
||||
Ok(quote!(
|
||||
#(#attrs)*
|
||||
#vis #mod_token #ident {
|
||||
#vis mod #ident {
|
||||
#(#items)*
|
||||
|
||||
#initialization
|
||||
|
||||
#[allow(unknown_lints, non_local_definitions)]
|
||||
impl MakeDef {
|
||||
const fn make_def() -> #pyo3_path::impl_::pymodule::ModuleDef {
|
||||
use #pyo3_path::impl_::pymodule as impl_;
|
||||
const INITIALIZER: impl_::ModuleInitializer = impl_::ModuleInitializer(__pyo3_pymodule);
|
||||
unsafe {
|
||||
impl_::ModuleDef::new(
|
||||
__PYO3_NAME,
|
||||
#doc,
|
||||
INITIALIZER
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn __pyo3_pymodule(module: &#pyo3_path::Bound<'_, #pyo3_path::types::PyModule>) -> #pyo3_path::PyResult<()> {
|
||||
use #pyo3_path::impl_::pymodule::PyAddToModule;
|
||||
#(
|
||||
|
@ -360,7 +271,7 @@ pub fn pymodule_module_impl(
|
|||
#module_items::_PYO3_DEF.add_to_module(module)?;
|
||||
)*
|
||||
#pymodule_init
|
||||
::std::result::Result::Ok(())
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
))
|
||||
|
@ -368,22 +279,17 @@ pub fn pymodule_module_impl(
|
|||
|
||||
/// Generates the function that is called by the python interpreter to initialize the native
|
||||
/// module
|
||||
pub fn pymodule_function_impl(
|
||||
function: &mut syn::ItemFn,
|
||||
mut options: PyModuleOptions,
|
||||
) -> Result<TokenStream> {
|
||||
options.take_pyo3_options(&mut function.attrs)?;
|
||||
process_functions_in_module(&options, function)?;
|
||||
let ctx = &Ctx::new(&options.krate, None);
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result<TokenStream> {
|
||||
let options = PyModuleOptions::from_attrs(&mut function.attrs)?;
|
||||
process_functions_in_module(&options, &mut function)?;
|
||||
let ctx = &Ctx::new(&options.krate);
|
||||
let stmts = std::mem::take(&mut function.block.stmts);
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let ident = &function.sig.ident;
|
||||
let name = options
|
||||
.name
|
||||
.map_or_else(|| ident.unraw(), |name| name.value.0);
|
||||
let vis = &function.vis;
|
||||
let doc = get_doc(&function.attrs, None, ctx);
|
||||
let doc = get_doc(&function.attrs, None);
|
||||
|
||||
let initialization = module_initialization(&name, ctx, quote! { MakeDef::make_def() }, false);
|
||||
let initialization = module_initialization(options, ident);
|
||||
|
||||
// Module function called with optional Python<'_> marker as first arg, followed by the module.
|
||||
let mut module_args = Vec::new();
|
||||
|
@ -393,7 +299,32 @@ pub fn pymodule_function_impl(
|
|||
module_args
|
||||
.push(quote!(::std::convert::Into::into(#pyo3_path::impl_::pymethods::BoundRef(module))));
|
||||
|
||||
let extractors = function
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.filter_map(|param| {
|
||||
if let syn::FnArg::Typed(pat_type) = param {
|
||||
if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
|
||||
let ident: &syn::Ident = &pat_ident.ident;
|
||||
return Some([
|
||||
parse_quote!{ let check_gil_refs = #pyo3_path::impl_::deprecations::GilRefs::new(); },
|
||||
parse_quote! { let #ident = #pyo3_path::impl_::deprecations::inspect_type(#ident, &check_gil_refs); },
|
||||
parse_quote_spanned! { pat_type.span() => check_gil_refs.function_arg(); },
|
||||
]);
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.flatten();
|
||||
|
||||
function.block.stmts = extractors.chain(stmts).collect();
|
||||
function
|
||||
.attrs
|
||||
.push(parse_quote!(#[allow(clippy::used_underscore_binding)]));
|
||||
|
||||
Ok(quote! {
|
||||
#function
|
||||
#[doc(hidden)]
|
||||
#vis mod #ident {
|
||||
#initialization
|
||||
|
@ -423,49 +354,38 @@ pub fn pymodule_function_impl(
|
|||
})
|
||||
}
|
||||
|
||||
fn module_initialization(
|
||||
name: &syn::Ident,
|
||||
ctx: &Ctx,
|
||||
module_def: TokenStream,
|
||||
is_submodule: bool,
|
||||
) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
fn module_initialization(options: PyModuleOptions, ident: &syn::Ident) -> TokenStream {
|
||||
let name = options.name.unwrap_or_else(|| ident.unraw());
|
||||
let ctx = &Ctx::new(&options.krate);
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let pyinit_symbol = format!("PyInit_{}", name);
|
||||
let name = name.to_string();
|
||||
let pyo3_name = LitCStr::new(CString::new(name).unwrap(), Span::call_site(), ctx);
|
||||
|
||||
let mut result = quote! {
|
||||
quote! {
|
||||
#[doc(hidden)]
|
||||
pub const __PYO3_NAME: &'static ::std::ffi::CStr = #pyo3_name;
|
||||
pub const __PYO3_NAME: &'static str = concat!(stringify!(#name), "\0");
|
||||
|
||||
pub(super) struct MakeDef;
|
||||
#[doc(hidden)]
|
||||
pub static _PYO3_DEF: #pyo3_path::impl_::pymodule::ModuleDef = #module_def;
|
||||
};
|
||||
if !is_submodule {
|
||||
result.extend(quote! {
|
||||
/// This autogenerated function is called by the python interpreter when importing
|
||||
/// the module.
|
||||
#[doc(hidden)]
|
||||
#[export_name = #pyinit_symbol]
|
||||
pub unsafe extern "C" fn __pyo3_init() -> *mut #pyo3_path::ffi::PyObject {
|
||||
#pyo3_path::impl_::trampoline::module_init(|py| _PYO3_DEF.make_module(py))
|
||||
}
|
||||
});
|
||||
pub static _PYO3_DEF: #pyo3_path::impl_::pymodule::ModuleDef = MakeDef::make_def();
|
||||
|
||||
/// This autogenerated function is called by the python interpreter when importing
|
||||
/// the module.
|
||||
#[doc(hidden)]
|
||||
#[export_name = #pyinit_symbol]
|
||||
pub unsafe extern "C" fn __pyo3_init() -> *mut #pyo3_path::ffi::PyObject {
|
||||
#pyo3_path::impl_::trampoline::module_init(|py| _PYO3_DEF.make_module(py))
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Finds and takes care of the #[pyfn(...)] in `#[pymodule]`
|
||||
fn process_functions_in_module(options: &PyModuleOptions, func: &mut syn::ItemFn) -> Result<()> {
|
||||
let ctx = &Ctx::new(&options.krate, None);
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let mut stmts: Vec<syn::Stmt> = Vec::new();
|
||||
|
||||
#[cfg(feature = "gil-refs")]
|
||||
let imports = quote!(use #pyo3_path::{PyNativeType, types::PyModuleMethods};);
|
||||
#[cfg(not(feature = "gil-refs"))]
|
||||
let imports = quote!(use #pyo3_path::types::PyModuleMethods;);
|
||||
let ctx = &Ctx::new(&options.krate);
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let mut stmts: Vec<syn::Stmt> = vec![syn::parse_quote!(
|
||||
#[allow(unknown_lints, unused_imports, redundant_imports)]
|
||||
use #pyo3_path::{PyNativeType, types::PyModuleMethods};
|
||||
)];
|
||||
|
||||
for mut stmt in func.block.stmts.drain(..) {
|
||||
if let syn::Stmt::Item(Item::Fn(func)) = &mut stmt {
|
||||
|
@ -475,11 +395,7 @@ fn process_functions_in_module(options: &PyModuleOptions, func: &mut syn::ItemFn
|
|||
let name = &func.sig.ident;
|
||||
let statements: Vec<syn::Stmt> = syn::parse_quote! {
|
||||
#wrapped_function
|
||||
{
|
||||
#[allow(unknown_lints, unused_imports, redundant_imports)]
|
||||
#imports
|
||||
#module_name.as_borrowed().add_function(#pyo3_path::wrap_pyfunction!(#name, #module_name.as_borrowed())?)?;
|
||||
}
|
||||
#module_name.as_borrowed().add_function(#pyo3_path::wrap_pyfunction!(#name, #module_name.as_borrowed())?)?;
|
||||
};
|
||||
stmts.extend(statements);
|
||||
}
|
||||
|
@ -565,78 +481,13 @@ fn find_and_remove_attribute(attrs: &mut Vec<syn::Attribute>, ident: &str) -> bo
|
|||
found
|
||||
}
|
||||
|
||||
enum IdentOrStr<'a> {
|
||||
Str(&'a str),
|
||||
Ident(syn::Ident),
|
||||
}
|
||||
|
||||
impl<'a> PartialEq<syn::Ident> for IdentOrStr<'a> {
|
||||
fn eq(&self, other: &syn::Ident) -> bool {
|
||||
match self {
|
||||
IdentOrStr::Str(s) => other == s,
|
||||
IdentOrStr::Ident(i) => other == i,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn has_attribute(attrs: &[syn::Attribute], ident: &str) -> bool {
|
||||
has_attribute_with_namespace(attrs, None, &[ident])
|
||||
}
|
||||
|
||||
fn has_attribute_with_namespace(
|
||||
attrs: &[syn::Attribute],
|
||||
crate_path: Option<&PyO3CratePath>,
|
||||
idents: &[&str],
|
||||
) -> bool {
|
||||
let mut segments = vec![];
|
||||
if let Some(c) = crate_path {
|
||||
match c {
|
||||
PyO3CratePath::Given(paths) => {
|
||||
for p in &paths.segments {
|
||||
segments.push(IdentOrStr::Ident(p.ident.clone()));
|
||||
}
|
||||
}
|
||||
PyO3CratePath::Default => segments.push(IdentOrStr::Str("pyo3")),
|
||||
}
|
||||
};
|
||||
for i in idents {
|
||||
segments.push(IdentOrStr::Str(i));
|
||||
}
|
||||
|
||||
attrs.iter().any(|attr| {
|
||||
segments
|
||||
.iter()
|
||||
.eq(attr.path().segments.iter().map(|v| &v.ident))
|
||||
})
|
||||
}
|
||||
|
||||
fn set_module_attribute(attrs: &mut Vec<syn::Attribute>, module_name: &str) {
|
||||
attrs.push(parse_quote!(#[pyo3(module = #module_name)]));
|
||||
}
|
||||
|
||||
fn has_pyo3_module_declared<T: Parse>(
|
||||
attrs: &[syn::Attribute],
|
||||
root_attribute_name: &str,
|
||||
is_module_option: impl Fn(&T) -> bool + Copy,
|
||||
) -> Result<bool> {
|
||||
for attr in attrs {
|
||||
if (attr.path().is_ident("pyo3") || attr.path().is_ident(root_attribute_name))
|
||||
&& matches!(attr.meta, Meta::List(_))
|
||||
{
|
||||
for option in &attr.parse_args_with(Punctuated::<T, Comma>::parse_terminated)? {
|
||||
if is_module_option(option) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
attrs.iter().any(|attr| attr.path().is_ident(ident))
|
||||
}
|
||||
|
||||
enum PyModulePyO3Option {
|
||||
Submodule(SubmoduleAttribute),
|
||||
Crate(CrateAttribute),
|
||||
Name(NameAttribute),
|
||||
Module(ModuleAttribute),
|
||||
}
|
||||
|
||||
impl Parse for PyModulePyO3Option {
|
||||
|
@ -646,10 +497,6 @@ impl Parse for PyModulePyO3Option {
|
|||
input.parse().map(PyModulePyO3Option::Name)
|
||||
} else if lookahead.peek(syn::Token![crate]) {
|
||||
input.parse().map(PyModulePyO3Option::Crate)
|
||||
} else if lookahead.peek(attributes::kw::module) {
|
||||
input.parse().map(PyModulePyO3Option::Module)
|
||||
} else if lookahead.peek(attributes::kw::submodule) {
|
||||
input.parse().map(PyModulePyO3Option::Submodule)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
use crate::utils::Ctx;
|
||||
use crate::{
|
||||
method::{FnArg, FnSpec, RegularArg},
|
||||
method::{FnArg, FnSpec},
|
||||
pyfunction::FunctionSignature,
|
||||
quotes::some_wrap,
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::Result;
|
||||
|
||||
pub struct Holders {
|
||||
holders: Vec<syn::Ident>,
|
||||
gil_refs_checkers: Vec<syn::Ident>,
|
||||
}
|
||||
|
||||
impl Holders {
|
||||
pub fn new() -> Self {
|
||||
Holders {
|
||||
holders: Vec::new(),
|
||||
gil_refs_checkers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,33 +28,71 @@ impl Holders {
|
|||
holder
|
||||
}
|
||||
|
||||
pub fn push_gil_refs_checker(&mut self, span: Span) -> syn::Ident {
|
||||
let gil_refs_checker = syn::Ident::new(
|
||||
&format!("gil_refs_checker_{}", self.gil_refs_checkers.len()),
|
||||
span,
|
||||
);
|
||||
self.gil_refs_checkers.push(gil_refs_checker.clone());
|
||||
gil_refs_checker
|
||||
}
|
||||
|
||||
pub fn init_holders(&self, ctx: &Ctx) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let holders = &self.holders;
|
||||
let gil_refs_checkers = &self.gil_refs_checkers;
|
||||
quote! {
|
||||
#[allow(clippy::let_unit_value)]
|
||||
#(let mut #holders = #pyo3_path::impl_::extract_argument::FunctionArgumentHolder::INIT;)*
|
||||
#(let #gil_refs_checkers = #pyo3_path::impl_::deprecations::GilRefs::new();)*
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_gil_refs(&self) -> TokenStream {
|
||||
self.gil_refs_checkers
|
||||
.iter()
|
||||
.map(|e| quote_spanned! { e.span() => #e.function_arg(); })
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if the argument list is simply (*args, **kwds).
|
||||
pub fn is_forwarded_args(signature: &FunctionSignature<'_>) -> bool {
|
||||
matches!(
|
||||
signature.arguments.as_slice(),
|
||||
[FnArg::VarArgs(..), FnArg::KwArgs(..),]
|
||||
[
|
||||
FnArg {
|
||||
is_varargs: true,
|
||||
..
|
||||
},
|
||||
FnArg {
|
||||
is_kwargs: true,
|
||||
..
|
||||
},
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn check_arg_for_gil_refs(
|
||||
tokens: TokenStream,
|
||||
gil_refs_checker: syn::Ident,
|
||||
ctx: &Ctx,
|
||||
) -> TokenStream {
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
quote! {
|
||||
#pyo3_path::impl_::deprecations::inspect_type(#tokens, &#gil_refs_checker)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn impl_arg_params(
|
||||
spec: &FnSpec<'_>,
|
||||
self_: Option<&syn::Type>,
|
||||
fastcall: bool,
|
||||
holders: &mut Holders,
|
||||
ctx: &Ctx,
|
||||
) -> (TokenStream, Vec<TokenStream>) {
|
||||
) -> Result<(TokenStream, Vec<TokenStream>)> {
|
||||
let args_array = syn::Ident::new("output", Span::call_site());
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
|
||||
let from_py_with = spec
|
||||
.signature
|
||||
|
@ -59,10 +100,13 @@ pub fn impl_arg_params(
|
|||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, arg)| {
|
||||
let from_py_with = &arg.from_py_with()?.value;
|
||||
let from_py_with_holder = format_ident!("from_py_with_{}", i);
|
||||
let from_py_with = &arg.attrs.from_py_with.as_ref()?.value;
|
||||
let from_py_with_holder =
|
||||
syn::Ident::new(&format!("from_py_with_{}", i), Span::call_site());
|
||||
Some(quote_spanned! { from_py_with.span() =>
|
||||
let #from_py_with_holder = #from_py_with;
|
||||
let e = #pyo3_path::impl_::deprecations::GilRefs::new();
|
||||
let #from_py_with_holder = #pyo3_path::impl_::deprecations::inspect_fn(#from_py_with, &e);
|
||||
e.from_py_with_arg();
|
||||
})
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
@ -75,16 +119,28 @@ pub fn impl_arg_params(
|
|||
.arguments
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, arg)| impl_arg_param(arg, i, &mut 0, holders, ctx))
|
||||
.collect();
|
||||
return (
|
||||
.map(|(i, arg)| {
|
||||
let from_py_with =
|
||||
syn::Ident::new(&format!("from_py_with_{}", i), Span::call_site());
|
||||
let arg_value = quote!(#args_array[0].as_deref());
|
||||
|
||||
impl_arg_param(arg, from_py_with, arg_value, holders, ctx).map(|tokens| {
|
||||
check_arg_for_gil_refs(
|
||||
tokens,
|
||||
holders.push_gil_refs_checker(arg.ty.span()),
|
||||
ctx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
return Ok((
|
||||
quote! {
|
||||
let _args = #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &_args);
|
||||
let _kwargs = #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr_or_opt(py, &_kwargs);
|
||||
#from_py_with
|
||||
},
|
||||
arg_convert,
|
||||
);
|
||||
));
|
||||
};
|
||||
|
||||
let positional_parameter_names = &spec.signature.python_signature.positional_parameters;
|
||||
|
@ -115,8 +171,18 @@ pub fn impl_arg_params(
|
|||
.arguments
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, arg)| impl_arg_param(arg, i, &mut option_pos, holders, ctx))
|
||||
.collect();
|
||||
.map(|(i, arg)| {
|
||||
let from_py_with = syn::Ident::new(&format!("from_py_with_{}", i), Span::call_site());
|
||||
let arg_value = quote!(#args_array[#option_pos].as_deref());
|
||||
if arg.is_regular() {
|
||||
option_pos += 1;
|
||||
}
|
||||
|
||||
impl_arg_param(arg, from_py_with, arg_value, holders, ctx).map(|tokens| {
|
||||
check_arg_for_gil_refs(tokens, holders.push_gil_refs_checker(arg.ty.span()), ctx)
|
||||
})
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
let args_handler = if spec.signature.python_signature.varargs.is_some() {
|
||||
quote! { #pyo3_path::impl_::extract_argument::TupleVarargs }
|
||||
|
@ -158,7 +224,7 @@ pub fn impl_arg_params(
|
|||
};
|
||||
|
||||
// create array of arguments, and then parse
|
||||
(
|
||||
Ok((
|
||||
quote! {
|
||||
const DESCRIPTION: #pyo3_path::impl_::extract_argument::FunctionDescription = #pyo3_path::impl_::extract_argument::FunctionDescription {
|
||||
cls_name: #cls_name,
|
||||
|
@ -173,64 +239,19 @@ pub fn impl_arg_params(
|
|||
#from_py_with
|
||||
},
|
||||
param_conversion,
|
||||
)
|
||||
}
|
||||
|
||||
fn impl_arg_param(
|
||||
arg: &FnArg<'_>,
|
||||
pos: usize,
|
||||
option_pos: &mut usize,
|
||||
holders: &mut Holders,
|
||||
ctx: &Ctx,
|
||||
) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let args_array = syn::Ident::new("output", Span::call_site());
|
||||
|
||||
match arg {
|
||||
FnArg::Regular(arg) => {
|
||||
let from_py_with = format_ident!("from_py_with_{}", pos);
|
||||
let arg_value = quote!(#args_array[#option_pos].as_deref());
|
||||
*option_pos += 1;
|
||||
impl_regular_arg_param(arg, from_py_with, arg_value, holders, ctx)
|
||||
}
|
||||
FnArg::VarArgs(arg) => {
|
||||
let holder = holders.push_holder(arg.name.span());
|
||||
let name_str = arg.name.to_string();
|
||||
quote_spanned! { arg.name.span() =>
|
||||
#pyo3_path::impl_::extract_argument::extract_argument(
|
||||
&_args,
|
||||
&mut #holder,
|
||||
#name_str
|
||||
)?
|
||||
}
|
||||
}
|
||||
FnArg::KwArgs(arg) => {
|
||||
let holder = holders.push_holder(arg.name.span());
|
||||
let name_str = arg.name.to_string();
|
||||
quote_spanned! { arg.name.span() =>
|
||||
#pyo3_path::impl_::extract_argument::extract_optional_argument(
|
||||
_kwargs.as_deref(),
|
||||
&mut #holder,
|
||||
#name_str,
|
||||
|| ::std::option::Option::None
|
||||
)?
|
||||
}
|
||||
}
|
||||
FnArg::Py(..) => quote! { py },
|
||||
FnArg::CancelHandle(..) => quote! { __cancel_handle },
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
/// Re option_pos: The option slice doesn't contain the py: Python argument, so the argument
|
||||
/// index and the index in option diverge when using py: Python
|
||||
pub(crate) fn impl_regular_arg_param(
|
||||
arg: &RegularArg<'_>,
|
||||
pub(crate) fn impl_arg_param(
|
||||
arg: &FnArg<'_>,
|
||||
from_py_with: syn::Ident,
|
||||
arg_value: TokenStream, // expected type: Option<&'a Bound<'py, PyAny>>
|
||||
holders: &mut Holders,
|
||||
ctx: &Ctx,
|
||||
) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
) -> Result<TokenStream> {
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let pyo3_path = pyo3_path.to_tokens_spanned(arg.ty.span());
|
||||
|
||||
// Use this macro inside this function, to ensure that all code generated here is associated
|
||||
|
@ -239,19 +260,64 @@ pub(crate) fn impl_regular_arg_param(
|
|||
($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => $($tokens)*) }
|
||||
}
|
||||
|
||||
let name_str = arg.name.to_string();
|
||||
let mut default = arg.default_value.as_ref().map(|expr| quote!(#expr));
|
||||
if arg.py {
|
||||
return Ok(quote! { py });
|
||||
}
|
||||
|
||||
if arg.is_cancel_handle {
|
||||
return Ok(quote! { __cancel_handle });
|
||||
}
|
||||
|
||||
let name = arg.name;
|
||||
let name_str = name.to_string();
|
||||
|
||||
if arg.is_varargs {
|
||||
ensure_spanned!(
|
||||
arg.optional.is_none(),
|
||||
arg.name.span() => "args cannot be optional"
|
||||
);
|
||||
let holder = holders.push_holder(arg.ty.span());
|
||||
return Ok(quote_arg_span! {
|
||||
#pyo3_path::impl_::extract_argument::extract_argument(
|
||||
&_args,
|
||||
&mut #holder,
|
||||
#name_str
|
||||
)?
|
||||
});
|
||||
} else if arg.is_kwargs {
|
||||
ensure_spanned!(
|
||||
arg.optional.is_some(),
|
||||
arg.name.span() => "kwargs must be Option<_>"
|
||||
);
|
||||
let holder = holders.push_holder(arg.name.span());
|
||||
return Ok(quote_arg_span! {
|
||||
#pyo3_path::impl_::extract_argument::extract_optional_argument(
|
||||
_kwargs.as_deref(),
|
||||
&mut #holder,
|
||||
#name_str,
|
||||
|| ::std::option::Option::None
|
||||
)?
|
||||
});
|
||||
}
|
||||
|
||||
let mut default = arg.default.as_ref().map(|expr| quote!(#expr));
|
||||
|
||||
// Option<T> arguments have special treatment: the default should be specified _without_ the
|
||||
// Some() wrapper. Maybe this should be changed in future?!
|
||||
if arg.option_wrapped_type.is_some() {
|
||||
if arg.optional.is_some() {
|
||||
default = Some(default.map_or_else(
|
||||
|| quote!(::std::option::Option::None),
|
||||
|tokens| some_wrap(tokens, ctx),
|
||||
));
|
||||
}
|
||||
|
||||
if arg.from_py_with.is_some() {
|
||||
let tokens = if arg
|
||||
.attrs
|
||||
.from_py_with
|
||||
.as_ref()
|
||||
.map(|attr| &attr.value)
|
||||
.is_some()
|
||||
{
|
||||
if let Some(default) = default {
|
||||
quote_arg_span! {
|
||||
#pyo3_path::impl_::extract_argument::from_py_with_with_default(
|
||||
|
@ -273,7 +339,7 @@ pub(crate) fn impl_regular_arg_param(
|
|||
)?
|
||||
}
|
||||
}
|
||||
} else if arg.option_wrapped_type.is_some() {
|
||||
} else if arg.optional.is_some() {
|
||||
let holder = holders.push_holder(arg.name.span());
|
||||
quote_arg_span! {
|
||||
#pyo3_path::impl_::extract_argument::extract_optional_argument(
|
||||
|
@ -308,5 +374,7 @@ pub(crate) fn impl_regular_arg_param(
|
|||
#name_str
|
||||
)?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,6 +4,7 @@ use crate::{
|
|||
self, get_pyo3_options, take_attributes, take_pyo3_options, CrateAttribute,
|
||||
FromPyWithAttribute, NameAttribute, TextSignatureAttribute,
|
||||
},
|
||||
deprecations::Deprecations,
|
||||
method::{self, CallingConvention, FnArg},
|
||||
pymethod::check_generic,
|
||||
};
|
||||
|
@ -17,7 +18,7 @@ use syn::{
|
|||
|
||||
mod signature;
|
||||
|
||||
pub use self::signature::{ConstructorAttribute, FunctionSignature, SignatureAttribute};
|
||||
pub use self::signature::{FunctionSignature, SignatureAttribute};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PyFunctionArgPyO3Attributes {
|
||||
|
@ -204,13 +205,10 @@ pub fn impl_wrap_pyfunction(
|
|||
krate,
|
||||
} = options;
|
||||
|
||||
let ctx = &Ctx::new(&krate, Some(&func.sig));
|
||||
let Ctx { pyo3_path, .. } = &ctx;
|
||||
let ctx = &Ctx::new(&krate);
|
||||
let Ctx { pyo3_path } = &ctx;
|
||||
|
||||
let python_name = name
|
||||
.as_ref()
|
||||
.map_or_else(|| &func.sig.ident, |name| &name.value.0)
|
||||
.unraw();
|
||||
let python_name = name.map_or_else(|| func.sig.ident.unraw(), |name| name.value.0);
|
||||
|
||||
let tp = if pass_module.is_some() {
|
||||
let span = match func.sig.inputs.first() {
|
||||
|
@ -251,6 +249,7 @@ pub fn impl_wrap_pyfunction(
|
|||
text_signature,
|
||||
asyncness: func.sig.asyncness,
|
||||
unsafety: func.sig.unsafety,
|
||||
deprecations: Deprecations::new(ctx),
|
||||
};
|
||||
|
||||
let vis = &func.vis;
|
||||
|
@ -258,7 +257,7 @@ pub fn impl_wrap_pyfunction(
|
|||
|
||||
let wrapper_ident = format_ident!("__pyfunction_{}", spec.name);
|
||||
let wrapper = spec.get_wrapper_function(&wrapper_ident, None, ctx)?;
|
||||
let methoddef = spec.get_methoddef(wrapper_ident, &spec.get_doc(&func.attrs, ctx), ctx);
|
||||
let methoddef = spec.get_methoddef(wrapper_ident, &spec.get_doc(&func.attrs), ctx);
|
||||
|
||||
let wrapped_pyfunction = quote! {
|
||||
|
||||
|
|
|
@ -10,10 +10,9 @@ use syn::{
|
|||
|
||||
use crate::{
|
||||
attributes::{kw, KeywordAttribute},
|
||||
method::{FnArg, RegularArg},
|
||||
method::FnArg,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Signature {
|
||||
paren_token: syn::token::Paren,
|
||||
pub items: Punctuated<SignatureItem, Token![,]>,
|
||||
|
@ -37,35 +36,35 @@ impl ToTokens for Signature {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SignatureItemArgument {
|
||||
pub ident: syn::Ident,
|
||||
pub eq_and_default: Option<(Token![=], syn::Expr)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SignatureItemPosargsSep {
|
||||
pub slash: Token![/],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SignatureItemVarargsSep {
|
||||
pub asterisk: Token![*],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SignatureItemVarargs {
|
||||
pub sep: SignatureItemVarargsSep,
|
||||
pub ident: syn::Ident,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SignatureItemKwargs {
|
||||
pub asterisks: (Token![*], Token![*]),
|
||||
pub ident: syn::Ident,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SignatureItem {
|
||||
Argument(Box<SignatureItemArgument>),
|
||||
PosargsSep(SignatureItemPosargsSep),
|
||||
|
@ -196,16 +195,6 @@ impl ToTokens for SignatureItemPosargsSep {
|
|||
}
|
||||
|
||||
pub type SignatureAttribute = KeywordAttribute<kw::signature, Signature>;
|
||||
pub type ConstructorAttribute = KeywordAttribute<kw::constructor, Signature>;
|
||||
|
||||
impl ConstructorAttribute {
|
||||
pub fn into_signature(self) -> SignatureAttribute {
|
||||
SignatureAttribute {
|
||||
kw: kw::signature(self.kw.span),
|
||||
value: self.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PythonSignature {
|
||||
|
@ -362,39 +351,36 @@ impl<'a> FunctionSignature<'a> {
|
|||
|
||||
let mut next_non_py_argument_checked = |name: &syn::Ident| {
|
||||
for fn_arg in args_iter.by_ref() {
|
||||
match fn_arg {
|
||||
crate::method::FnArg::Py(..) => {
|
||||
// If the user incorrectly tried to include py: Python in the
|
||||
// signature, give a useful error as a hint.
|
||||
ensure_spanned!(
|
||||
name != fn_arg.name(),
|
||||
name.span() => "arguments of type `Python` must not be part of the signature"
|
||||
);
|
||||
// Otherwise try next argument.
|
||||
continue;
|
||||
}
|
||||
crate::method::FnArg::CancelHandle(..) => {
|
||||
// If the user incorrectly tried to include cancel: CoroutineCancel in the
|
||||
// signature, give a useful error as a hint.
|
||||
ensure_spanned!(
|
||||
name != fn_arg.name(),
|
||||
name.span() => "`cancel_handle` argument must not be part of the signature"
|
||||
);
|
||||
// Otherwise try next argument.
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
ensure_spanned!(
|
||||
name == fn_arg.name(),
|
||||
name.span() => format!(
|
||||
"expected argument from function definition `{}` but got argument `{}`",
|
||||
fn_arg.name().unraw(),
|
||||
name.unraw(),
|
||||
)
|
||||
);
|
||||
return Ok(fn_arg);
|
||||
}
|
||||
if fn_arg.py {
|
||||
// If the user incorrectly tried to include py: Python in the
|
||||
// signature, give a useful error as a hint.
|
||||
ensure_spanned!(
|
||||
name != fn_arg.name,
|
||||
name.span() => "arguments of type `Python` must not be part of the signature"
|
||||
);
|
||||
// Otherwise try next argument.
|
||||
continue;
|
||||
}
|
||||
if fn_arg.is_cancel_handle {
|
||||
// If the user incorrectly tried to include cancel: CoroutineCancel in the
|
||||
// signature, give a useful error as a hint.
|
||||
ensure_spanned!(
|
||||
name != fn_arg.name,
|
||||
name.span() => "`cancel_handle` argument must not be part of the signature"
|
||||
);
|
||||
// Otherwise try next argument.
|
||||
continue;
|
||||
}
|
||||
|
||||
ensure_spanned!(
|
||||
name == fn_arg.name,
|
||||
name.span() => format!(
|
||||
"expected argument from function definition `{}` but got argument `{}`",
|
||||
fn_arg.name.unraw(),
|
||||
name.unraw(),
|
||||
)
|
||||
);
|
||||
return Ok(fn_arg);
|
||||
}
|
||||
bail_spanned!(
|
||||
name.span() => "signature entry does not have a corresponding function argument"
|
||||
|
@ -412,15 +398,7 @@ impl<'a> FunctionSignature<'a> {
|
|||
arg.span(),
|
||||
)?;
|
||||
if let Some((_, default)) = &arg.eq_and_default {
|
||||
if let FnArg::Regular(arg) = fn_arg {
|
||||
arg.default_value = Some(default.clone());
|
||||
} else {
|
||||
unreachable!(
|
||||
"`Python` and `CancelHandle` are already handled above and `*args`/`**kwargs` are \
|
||||
parsed and transformed below. Because the have to come last and are only allowed \
|
||||
once, this has to be a regular argument."
|
||||
);
|
||||
}
|
||||
fn_arg.default = Some(default.clone());
|
||||
}
|
||||
}
|
||||
SignatureItem::VarargsSep(sep) => {
|
||||
|
@ -428,12 +406,12 @@ impl<'a> FunctionSignature<'a> {
|
|||
}
|
||||
SignatureItem::Varargs(varargs) => {
|
||||
let fn_arg = next_non_py_argument_checked(&varargs.ident)?;
|
||||
fn_arg.to_varargs_mut()?;
|
||||
fn_arg.is_varargs = true;
|
||||
parse_state.add_varargs(&mut python_signature, varargs)?;
|
||||
}
|
||||
SignatureItem::Kwargs(kwargs) => {
|
||||
let fn_arg = next_non_py_argument_checked(&kwargs.ident)?;
|
||||
fn_arg.to_kwargs_mut()?;
|
||||
fn_arg.is_kwargs = true;
|
||||
parse_state.add_kwargs(&mut python_signature, kwargs)?;
|
||||
}
|
||||
SignatureItem::PosargsSep(sep) => {
|
||||
|
@ -443,11 +421,9 @@ impl<'a> FunctionSignature<'a> {
|
|||
}
|
||||
|
||||
// Ensure no non-py arguments remain
|
||||
if let Some(arg) =
|
||||
args_iter.find(|arg| !matches!(arg, FnArg::Py(..) | FnArg::CancelHandle(..)))
|
||||
{
|
||||
if let Some(arg) = args_iter.find(|arg| !arg.py && !arg.is_cancel_handle) {
|
||||
bail_spanned!(
|
||||
attribute.kw.span() => format!("missing signature entry for argument `{}`", arg.name())
|
||||
attribute.kw.span() => format!("missing signature entry for argument `{}`", arg.name)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -463,20 +439,15 @@ impl<'a> FunctionSignature<'a> {
|
|||
let mut python_signature = PythonSignature::default();
|
||||
for arg in &arguments {
|
||||
// Python<'_> arguments don't show in Python signature
|
||||
if matches!(arg, FnArg::Py(..) | FnArg::CancelHandle(..)) {
|
||||
if arg.py || arg.is_cancel_handle {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let FnArg::Regular(RegularArg {
|
||||
ty,
|
||||
option_wrapped_type: None,
|
||||
..
|
||||
}) = arg
|
||||
{
|
||||
if arg.optional.is_none() {
|
||||
// This argument is required, all previous arguments must also have been required
|
||||
ensure_spanned!(
|
||||
python_signature.required_positional_parameters == python_signature.positional_parameters.len(),
|
||||
ty.span() => "required arguments after an `Option<_>` argument are ambiguous\n\
|
||||
arg.ty.span() => "required arguments after an `Option<_>` argument are ambiguous\n\
|
||||
= help: add a `#[pyo3(signature)]` annotation on this function to unambiguously specify the default values for all optional parameters"
|
||||
);
|
||||
|
||||
|
@ -486,7 +457,7 @@ impl<'a> FunctionSignature<'a> {
|
|||
|
||||
python_signature
|
||||
.positional_parameters
|
||||
.push(arg.name().unraw().to_string());
|
||||
.push(arg.name.unraw().to_string());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
|
@ -498,12 +469,8 @@ impl<'a> FunctionSignature<'a> {
|
|||
|
||||
fn default_value_for_parameter(&self, parameter: &str) -> String {
|
||||
let mut default = "...".to_string();
|
||||
if let Some(fn_arg) = self.arguments.iter().find(|arg| arg.name() == parameter) {
|
||||
if let FnArg::Regular(RegularArg {
|
||||
default_value: Some(arg_default),
|
||||
..
|
||||
}) = fn_arg
|
||||
{
|
||||
if let Some(fn_arg) = self.arguments.iter().find(|arg| arg.name == parameter) {
|
||||
if let Some(arg_default) = fn_arg.default.as_ref() {
|
||||
match arg_default {
|
||||
// literal values
|
||||
syn::Expr::Lit(syn::ExprLit { lit, .. }) => match lit {
|
||||
|
@ -529,11 +496,7 @@ impl<'a> FunctionSignature<'a> {
|
|||
// others, unsupported yet so defaults to `...`
|
||||
_ => {}
|
||||
}
|
||||
} else if let FnArg::Regular(RegularArg {
|
||||
option_wrapped_type: Some(..),
|
||||
..
|
||||
}) = fn_arg
|
||||
{
|
||||
} else if fn_arg.optional.is_some() {
|
||||
// functions without a `#[pyo3(signature = (...))]` option
|
||||
// will treat trailing `Option<T>` arguments as having a default of `None`
|
||||
default = "None".to_string();
|
||||
|
|
|
@ -90,6 +90,7 @@ pub fn impl_methods(
|
|||
methods_type: PyClassMethodsType,
|
||||
options: PyImplOptions,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let ctx = &Ctx::new(&options.krate);
|
||||
let mut trait_impls = Vec::new();
|
||||
let mut proto_impls = Vec::new();
|
||||
let mut methods = Vec::new();
|
||||
|
@ -100,7 +101,6 @@ pub fn impl_methods(
|
|||
for iimpl in impls {
|
||||
match iimpl {
|
||||
syn::ImplItem::Fn(meth) => {
|
||||
let ctx = &Ctx::new(&options.krate, Some(&meth.sig));
|
||||
let mut fun_options = PyFunctionOptions::from_attrs(&mut meth.attrs)?;
|
||||
fun_options.krate = fun_options.krate.or_else(|| options.krate.clone());
|
||||
match pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs, fun_options, ctx)?
|
||||
|
@ -129,8 +129,7 @@ pub fn impl_methods(
|
|||
}
|
||||
}
|
||||
syn::ImplItem::Const(konst) => {
|
||||
let ctx = &Ctx::new(&options.krate, None);
|
||||
let attributes = ConstAttributes::from_attrs(&mut konst.attrs)?;
|
||||
let attributes = ConstAttributes::from_attrs(&mut konst.attrs, ctx)?;
|
||||
if attributes.is_class_attr {
|
||||
let spec = ConstSpec {
|
||||
rust_ident: konst.ident.clone(),
|
||||
|
@ -160,10 +159,11 @@ pub fn impl_methods(
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
let ctx = &Ctx::new(&options.krate, None);
|
||||
|
||||
add_shared_proto_slots(ty, &mut proto_impls, implemented_proto_fragments, ctx);
|
||||
|
||||
let ctx = &Ctx::new(&options.krate);
|
||||
|
||||
let items = match methods_type {
|
||||
PyClassMethodsType::Specialization => impl_py_methods(ty, methods, proto_impls, ctx),
|
||||
PyClassMethodsType::Inventory => submit_methods_inventory(ty, methods, proto_impls, ctx),
|
||||
|
@ -182,27 +182,27 @@ pub fn impl_methods(
|
|||
})
|
||||
}
|
||||
|
||||
pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec, ctx: &Ctx) -> MethodAndMethodDef {
|
||||
pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec<'_>, ctx: &Ctx) -> MethodAndMethodDef {
|
||||
let member = &spec.rust_ident;
|
||||
let wrapper_ident = format_ident!("__pymethod_{}__", member);
|
||||
let python_name = spec.null_terminated_python_name(ctx);
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let deprecations = &spec.attributes.deprecations;
|
||||
let python_name = &spec.null_terminated_python_name();
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
|
||||
let associated_method = quote! {
|
||||
fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> {
|
||||
#deprecations
|
||||
::std::result::Result::Ok(#pyo3_path::IntoPy::into_py(#cls::#member, py))
|
||||
}
|
||||
};
|
||||
|
||||
let method_def = quote! {
|
||||
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
|
||||
#pyo3_path::class::PyMethodDefType::ClassAttribute({
|
||||
#pyo3_path::class::PyClassAttributeDef::new(
|
||||
#python_name,
|
||||
#cls::#wrapper_ident
|
||||
)
|
||||
})
|
||||
)
|
||||
#pyo3_path::class::PyMethodDefType::ClassAttribute({
|
||||
#pyo3_path::class::PyClassAttributeDef::new(
|
||||
#python_name,
|
||||
#pyo3_path::impl_::pymethods::PyClassAttributeFactory(#cls::#wrapper_ident)
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
MethodAndMethodDef {
|
||||
|
@ -217,9 +217,8 @@ fn impl_py_methods(
|
|||
proto_impls: Vec<TokenStream>,
|
||||
ctx: &Ctx,
|
||||
) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
quote! {
|
||||
#[allow(unknown_lints, non_local_definitions)]
|
||||
impl #pyo3_path::impl_::pyclass::PyMethods<#ty>
|
||||
for #pyo3_path::impl_::pyclass::PyClassImplCollector<#ty>
|
||||
{
|
||||
|
@ -240,7 +239,7 @@ fn add_shared_proto_slots(
|
|||
mut implemented_proto_fragments: HashSet<String>,
|
||||
ctx: &Ctx,
|
||||
) {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
macro_rules! try_add_shared_slot {
|
||||
($slot:ident, $($fragments:literal),*) => {{
|
||||
let mut implemented = false;
|
||||
|
@ -298,7 +297,7 @@ fn submit_methods_inventory(
|
|||
proto_impls: Vec<TokenStream>,
|
||||
ctx: &Ctx,
|
||||
) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
quote! {
|
||||
#pyo3_path::inventory::submit! {
|
||||
type Inventory = <#ty as #pyo3_path::impl_::pyclass::PyClassImpl>::Inventory;
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
use std::borrow::Cow;
|
||||
use std::ffi::CString;
|
||||
|
||||
use crate::attributes::{NameAttribute, RenamingRule};
|
||||
use crate::deprecations::deprecate_trailing_option_default;
|
||||
use crate::method::{CallingConvention, ExtractErrorMode, PyArg};
|
||||
use crate::params::{impl_regular_arg_param, Holders};
|
||||
use crate::method::{CallingConvention, ExtractErrorMode};
|
||||
use crate::params::{check_arg_for_gil_refs, impl_arg_param, Holders};
|
||||
use crate::utils::Ctx;
|
||||
use crate::utils::PythonDoc;
|
||||
use crate::utils::{Ctx, LitCStr};
|
||||
use crate::{
|
||||
method::{FnArg, FnSpec, FnType, SelfType},
|
||||
pyfunction::PyFunctionOptions,
|
||||
|
@ -164,8 +162,9 @@ impl<'a> PyMethod<'a> {
|
|||
sig: &'a mut syn::Signature,
|
||||
meth_attrs: &mut Vec<syn::Attribute>,
|
||||
options: PyFunctionOptions,
|
||||
ctx: &'a Ctx,
|
||||
) -> Result<Self> {
|
||||
let spec = FnSpec::parse(sig, meth_attrs, options)?;
|
||||
let spec = FnSpec::parse(sig, meth_attrs, options, ctx)?;
|
||||
|
||||
let method_name = spec.python_name.to_string();
|
||||
let kind = PyMethodKind::from_name(&method_name);
|
||||
|
@ -194,9 +193,9 @@ pub fn gen_py_method(
|
|||
) -> Result<GeneratedPyMethod> {
|
||||
check_generic(sig)?;
|
||||
ensure_function_options_valid(&options)?;
|
||||
let method = PyMethod::parse(sig, meth_attrs, options)?;
|
||||
let method = PyMethod::parse(sig, meth_attrs, options, ctx)?;
|
||||
let spec = &method.spec;
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
|
||||
Ok(match (method.kind, &spec.tp) {
|
||||
// Class attributes go before protos so that class attributes can be used to set proto
|
||||
|
@ -227,21 +226,21 @@ pub fn gen_py_method(
|
|||
(_, FnType::Fn(_)) => GeneratedPyMethod::Method(impl_py_method_def(
|
||||
cls,
|
||||
spec,
|
||||
&spec.get_doc(meth_attrs, ctx),
|
||||
&spec.get_doc(meth_attrs),
|
||||
None,
|
||||
ctx,
|
||||
)?),
|
||||
(_, FnType::FnClass(_)) => GeneratedPyMethod::Method(impl_py_method_def(
|
||||
cls,
|
||||
spec,
|
||||
&spec.get_doc(meth_attrs, ctx),
|
||||
&spec.get_doc(meth_attrs),
|
||||
Some(quote!(#pyo3_path::ffi::METH_CLASS)),
|
||||
ctx,
|
||||
)?),
|
||||
(_, FnType::FnStatic) => GeneratedPyMethod::Method(impl_py_method_def(
|
||||
cls,
|
||||
spec,
|
||||
&spec.get_doc(meth_attrs, ctx),
|
||||
&spec.get_doc(meth_attrs),
|
||||
Some(quote!(#pyo3_path::ffi::METH_STATIC)),
|
||||
ctx,
|
||||
)?),
|
||||
|
@ -255,7 +254,7 @@ pub fn gen_py_method(
|
|||
PropertyType::Function {
|
||||
self_type,
|
||||
spec,
|
||||
doc: spec.get_doc(meth_attrs, ctx),
|
||||
doc: spec.get_doc(meth_attrs),
|
||||
},
|
||||
ctx,
|
||||
)?),
|
||||
|
@ -264,7 +263,7 @@ pub fn gen_py_method(
|
|||
PropertyType::Function {
|
||||
self_type,
|
||||
spec,
|
||||
doc: spec.get_doc(meth_attrs, ctx),
|
||||
doc: spec.get_doc(meth_attrs),
|
||||
},
|
||||
ctx,
|
||||
)?),
|
||||
|
@ -318,7 +317,7 @@ pub fn impl_py_method_def(
|
|||
flags: Option<TokenStream>,
|
||||
ctx: &Ctx,
|
||||
) -> Result<MethodAndMethodDef> {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let wrapper_ident = format_ident!("__pymethod_{}__", spec.python_name);
|
||||
let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?;
|
||||
let add_flags = flags.map(|flags| quote!(.flags(#flags)));
|
||||
|
@ -329,9 +328,7 @@ pub fn impl_py_method_def(
|
|||
};
|
||||
let methoddef = spec.get_methoddef(quote! { #cls::#wrapper_ident }, doc, ctx);
|
||||
let method_def = quote! {
|
||||
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
|
||||
#pyo3_path::class::PyMethodDefType::#methoddef_type(#methoddef #add_flags)
|
||||
)
|
||||
#pyo3_path::class::PyMethodDefType::#methoddef_type(#methoddef #add_flags)
|
||||
};
|
||||
Ok(MethodAndMethodDef {
|
||||
associated_method,
|
||||
|
@ -345,7 +342,7 @@ pub fn impl_py_method_def_new(
|
|||
spec: &FnSpec<'_>,
|
||||
ctx: &Ctx,
|
||||
) -> Result<MethodAndSlotDef> {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let wrapper_ident = syn::Ident::new("__pymethod___new____", Span::call_site());
|
||||
let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?;
|
||||
// Use just the text_signature_call_signature() because the class' Python name
|
||||
|
@ -355,6 +352,7 @@ pub fn impl_py_method_def_new(
|
|||
|| quote!(::std::option::Option::None),
|
||||
|text_signature| quote!(::std::option::Option::Some(#text_signature)),
|
||||
);
|
||||
let deprecations = &spec.deprecations;
|
||||
let slot_def = quote! {
|
||||
#pyo3_path::ffi::PyType_Slot {
|
||||
slot: #pyo3_path::ffi::Py_tp_new,
|
||||
|
@ -363,7 +361,10 @@ pub fn impl_py_method_def_new(
|
|||
subtype: *mut #pyo3_path::ffi::PyTypeObject,
|
||||
args: *mut #pyo3_path::ffi::PyObject,
|
||||
kwargs: *mut #pyo3_path::ffi::PyObject,
|
||||
) -> *mut #pyo3_path::ffi::PyObject {
|
||||
) -> *mut #pyo3_path::ffi::PyObject
|
||||
{
|
||||
#deprecations
|
||||
|
||||
use #pyo3_path::impl_::pyclass::*;
|
||||
#[allow(unknown_lints, non_local_definitions)]
|
||||
impl PyClassNewTextSignature<#cls> for PyClassImplCollector<#cls> {
|
||||
|
@ -391,7 +392,7 @@ pub fn impl_py_method_def_new(
|
|||
}
|
||||
|
||||
fn impl_call_slot(cls: &syn::Type, mut spec: FnSpec<'_>, ctx: &Ctx) -> Result<MethodAndSlotDef> {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
|
||||
// HACK: __call__ proto slot must always use varargs calling convention, so change the spec.
|
||||
// Probably indicates there's a refactoring opportunity somewhere.
|
||||
|
@ -431,7 +432,7 @@ fn impl_traverse_slot(
|
|||
spec: &FnSpec<'_>,
|
||||
ctx: &Ctx,
|
||||
) -> syn::Result<MethodAndSlotDef> {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
if let (Some(py_arg), _) = split_off_python_arg(&spec.signature.arguments) {
|
||||
return Err(syn::Error::new_spanned(py_arg.ty, "__traverse__ may not take `Python`. \
|
||||
Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \
|
||||
|
@ -482,11 +483,11 @@ fn impl_py_class_attribute(
|
|||
spec: &FnSpec<'_>,
|
||||
ctx: &Ctx,
|
||||
) -> syn::Result<MethodAndMethodDef> {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let (py_arg, args) = split_off_python_arg(&spec.signature.arguments);
|
||||
ensure_spanned!(
|
||||
args.is_empty(),
|
||||
args[0].ty().span() => "#[classattr] can only have one argument (of type pyo3::Python)"
|
||||
args[0].ty.span() => "#[classattr] can only have one argument (of type pyo3::Python)"
|
||||
);
|
||||
|
||||
let name = &spec.name;
|
||||
|
@ -497,7 +498,7 @@ fn impl_py_class_attribute(
|
|||
};
|
||||
|
||||
let wrapper_ident = format_ident!("__pymethod_{}__", name);
|
||||
let python_name = spec.null_terminated_python_name(ctx);
|
||||
let python_name = spec.null_terminated_python_name();
|
||||
let body = quotes::ok_wrap(fncall, ctx);
|
||||
|
||||
let associated_method = quote! {
|
||||
|
@ -508,14 +509,12 @@ fn impl_py_class_attribute(
|
|||
};
|
||||
|
||||
let method_def = quote! {
|
||||
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
|
||||
#pyo3_path::class::PyMethodDefType::ClassAttribute({
|
||||
#pyo3_path::class::PyClassAttributeDef::new(
|
||||
#python_name,
|
||||
#cls::#wrapper_ident
|
||||
)
|
||||
})
|
||||
)
|
||||
#pyo3_path::class::PyMethodDefType::ClassAttribute({
|
||||
#pyo3_path::class::PyClassAttributeDef::new(
|
||||
#python_name,
|
||||
#pyo3_path::impl_::pymethods::PyClassAttributeFactory(#cls::#wrapper_ident)
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
Ok(MethodAndMethodDef {
|
||||
|
@ -538,7 +537,7 @@ fn impl_call_setter(
|
|||
bail_spanned!(spec.name.span() => "setter function expected to have one argument");
|
||||
} else if args.len() > 1 {
|
||||
bail_spanned!(
|
||||
args[1].ty().span() =>
|
||||
args[1].ty.span() =>
|
||||
"setter function can have at most two arguments ([pyo3::Python,] and value)"
|
||||
);
|
||||
}
|
||||
|
@ -559,9 +558,9 @@ pub fn impl_py_setter_def(
|
|||
property_type: PropertyType<'_>,
|
||||
ctx: &Ctx,
|
||||
) -> Result<MethodAndMethodDef> {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let python_name = property_type.null_terminated_python_name(ctx)?;
|
||||
let doc = property_type.doc(ctx);
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let python_name = property_type.null_terminated_python_name()?;
|
||||
let doc = property_type.doc();
|
||||
let mut holders = Holders::new();
|
||||
let setter_impl = match property_type {
|
||||
PropertyType::Descriptor {
|
||||
|
@ -607,36 +606,37 @@ pub fn impl_py_setter_def(
|
|||
PropertyType::Function { spec, .. } => {
|
||||
let (_, args) = split_off_python_arg(&spec.signature.arguments);
|
||||
let value_arg = &args[0];
|
||||
let (from_py_with, ident) =
|
||||
if let Some(from_py_with) = &value_arg.from_py_with().as_ref().map(|f| &f.value) {
|
||||
let ident = syn::Ident::new("from_py_with", from_py_with.span());
|
||||
(
|
||||
quote_spanned! { from_py_with.span() =>
|
||||
let #ident = #from_py_with;
|
||||
},
|
||||
ident,
|
||||
)
|
||||
} else {
|
||||
(quote!(), syn::Ident::new("dummy", Span::call_site()))
|
||||
};
|
||||
|
||||
let arg = if let FnArg::Regular(arg) = &value_arg {
|
||||
arg
|
||||
let (from_py_with, ident) = if let Some(from_py_with) =
|
||||
&value_arg.attrs.from_py_with.as_ref().map(|f| &f.value)
|
||||
{
|
||||
let ident = syn::Ident::new("from_py_with", from_py_with.span());
|
||||
(
|
||||
quote_spanned! { from_py_with.span() =>
|
||||
let e = #pyo3_path::impl_::deprecations::GilRefs::new();
|
||||
let #ident = #pyo3_path::impl_::deprecations::inspect_fn(#from_py_with, &e);
|
||||
e.from_py_with_arg();
|
||||
},
|
||||
ident,
|
||||
)
|
||||
} else {
|
||||
bail_spanned!(value_arg.name().span() => "The #[setter] value argument can't be *args, **kwargs or `cancel_handle`.");
|
||||
(quote!(), syn::Ident::new("dummy", Span::call_site()))
|
||||
};
|
||||
|
||||
let extract = impl_regular_arg_param(
|
||||
arg,
|
||||
let extract = impl_arg_param(
|
||||
&args[0],
|
||||
ident,
|
||||
quote!(::std::option::Option::Some(_value.into())),
|
||||
&mut holders,
|
||||
ctx,
|
||||
);
|
||||
|
||||
let deprecation = deprecate_trailing_option_default(spec);
|
||||
)
|
||||
.map(|tokens| {
|
||||
check_arg_for_gil_refs(
|
||||
tokens,
|
||||
holders.push_gil_refs_checker(value_arg.ty.span()),
|
||||
ctx,
|
||||
)
|
||||
})?;
|
||||
quote! {
|
||||
#deprecation
|
||||
#from_py_with
|
||||
let _val = #extract;
|
||||
}
|
||||
|
@ -650,8 +650,12 @@ pub fn impl_py_setter_def(
|
|||
.unwrap_or_default();
|
||||
|
||||
let holder = holders.push_holder(span);
|
||||
let gil_refs_checker = holders.push_gil_refs_checker(span);
|
||||
quote! {
|
||||
let _val = #pyo3_path::impl_::extract_argument::extract_argument(_value.into(), &mut #holder, #name)?;
|
||||
let _val = #pyo3_path::impl_::deprecations::inspect_type(
|
||||
#pyo3_path::impl_::extract_argument::extract_argument(_value.into(), &mut #holder, #name)?,
|
||||
&#gil_refs_checker
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -668,6 +672,7 @@ pub fn impl_py_setter_def(
|
|||
}
|
||||
|
||||
let init_holders = holders.init_holders(ctx);
|
||||
let check_gil_refs = holders.check_gil_refs();
|
||||
let associated_method = quote! {
|
||||
#cfg_attrs
|
||||
unsafe fn #wrapper_ident(
|
||||
|
@ -683,19 +688,18 @@ pub fn impl_py_setter_def(
|
|||
#init_holders
|
||||
#extract
|
||||
let result = #setter_impl;
|
||||
#check_gil_refs
|
||||
#pyo3_path::callback::convert(py, result)
|
||||
}
|
||||
};
|
||||
|
||||
let method_def = quote! {
|
||||
#cfg_attrs
|
||||
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
|
||||
#pyo3_path::class::PyMethodDefType::Setter(
|
||||
#pyo3_path::class::PySetterDef::new(
|
||||
#python_name,
|
||||
#cls::#wrapper_ident,
|
||||
#doc
|
||||
)
|
||||
#pyo3_path::class::PyMethodDefType::Setter(
|
||||
#pyo3_path::class::PySetterDef::new(
|
||||
#python_name,
|
||||
#pyo3_path::impl_::pymethods::PySetter(#cls::#wrapper_ident),
|
||||
#doc
|
||||
)
|
||||
)
|
||||
};
|
||||
|
@ -717,7 +721,7 @@ fn impl_call_getter(
|
|||
let slf = self_type.receiver(cls, ExtractErrorMode::Raise, holders, ctx);
|
||||
ensure_spanned!(
|
||||
args.is_empty(),
|
||||
args[0].ty().span() => "getter function can only have one argument (of type pyo3::Python)"
|
||||
args[0].ty.span() => "getter function can only have one argument (of type pyo3::Python)"
|
||||
);
|
||||
|
||||
let name = &spec.name;
|
||||
|
@ -736,9 +740,64 @@ pub fn impl_py_getter_def(
|
|||
property_type: PropertyType<'_>,
|
||||
ctx: &Ctx,
|
||||
) -> Result<MethodAndMethodDef> {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let python_name = property_type.null_terminated_python_name(ctx)?;
|
||||
let doc = property_type.doc(ctx);
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let python_name = property_type.null_terminated_python_name()?;
|
||||
let doc = property_type.doc();
|
||||
|
||||
let mut holders = Holders::new();
|
||||
let body = match property_type {
|
||||
PropertyType::Descriptor {
|
||||
field_index, field, ..
|
||||
} => {
|
||||
let slf = SelfType::Receiver {
|
||||
mutable: false,
|
||||
span: Span::call_site(),
|
||||
}
|
||||
.receiver(cls, ExtractErrorMode::Raise, &mut holders, ctx);
|
||||
let field_token = if let Some(ident) = &field.ident {
|
||||
// named struct field
|
||||
ident.to_token_stream()
|
||||
} else {
|
||||
// tuple struct field
|
||||
syn::Index::from(field_index).to_token_stream()
|
||||
};
|
||||
quotes::map_result_into_ptr(
|
||||
quotes::ok_wrap(
|
||||
quote! {
|
||||
::std::clone::Clone::clone(&(#slf.#field_token))
|
||||
},
|
||||
ctx,
|
||||
),
|
||||
ctx,
|
||||
)
|
||||
}
|
||||
// Forward to `IntoPyCallbackOutput`, to handle `#[getter]`s returning results.
|
||||
PropertyType::Function {
|
||||
spec, self_type, ..
|
||||
} => {
|
||||
let call = impl_call_getter(cls, spec, self_type, &mut holders, ctx)?;
|
||||
quote! {
|
||||
#pyo3_path::callback::convert(py, #call)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let wrapper_ident = match property_type {
|
||||
PropertyType::Descriptor {
|
||||
field: syn::Field {
|
||||
ident: Some(ident), ..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
format_ident!("__pymethod_get_{}__", ident)
|
||||
}
|
||||
PropertyType::Descriptor { field_index, .. } => {
|
||||
format_ident!("__pymethod_get_field_{}__", field_index)
|
||||
}
|
||||
PropertyType::Function { spec, .. } => {
|
||||
format_ident!("__pymethod_get_{}__", spec.name)
|
||||
}
|
||||
};
|
||||
|
||||
let mut cfg_attrs = TokenStream::new();
|
||||
if let PropertyType::Descriptor { field, .. } = &property_type {
|
||||
|
@ -751,100 +810,42 @@ pub fn impl_py_getter_def(
|
|||
}
|
||||
}
|
||||
|
||||
let mut holders = Holders::new();
|
||||
match property_type {
|
||||
PropertyType::Descriptor {
|
||||
field_index, field, ..
|
||||
} => {
|
||||
let ty = &field.ty;
|
||||
let field = if let Some(ident) = &field.ident {
|
||||
ident.to_token_stream()
|
||||
} else {
|
||||
syn::Index::from(field_index).to_token_stream()
|
||||
};
|
||||
|
||||
// TODO: on MSRV 1.77+, we can use `::std::mem::offset_of!` here, and it should
|
||||
// make it possible for the `MaybeRuntimePyMethodDef` to be a `Static` variant.
|
||||
let method_def = quote_spanned! {ty.span()=>
|
||||
#cfg_attrs
|
||||
{
|
||||
#[allow(unused_imports)] // might not be used if all probes are positve
|
||||
use #pyo3_path::impl_::pyclass::Probe;
|
||||
|
||||
struct Offset;
|
||||
unsafe impl #pyo3_path::impl_::pyclass::OffsetCalculator<#cls, #ty> for Offset {
|
||||
fn offset() -> usize {
|
||||
#pyo3_path::impl_::pyclass::class_offset::<#cls>() +
|
||||
#pyo3_path::impl_::pyclass::offset_of!(#cls, #field)
|
||||
}
|
||||
}
|
||||
|
||||
const GENERATOR: #pyo3_path::impl_::pyclass::PyClassGetterGenerator::<
|
||||
#cls,
|
||||
#ty,
|
||||
Offset,
|
||||
{ #pyo3_path::impl_::pyclass::IsPyT::<#ty>::VALUE },
|
||||
{ #pyo3_path::impl_::pyclass::IsToPyObject::<#ty>::VALUE },
|
||||
> = unsafe { #pyo3_path::impl_::pyclass::PyClassGetterGenerator::new() };
|
||||
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Runtime(
|
||||
|| GENERATOR.generate(#python_name, #doc)
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(MethodAndMethodDef {
|
||||
associated_method: quote! {},
|
||||
method_def,
|
||||
})
|
||||
let init_holders = holders.init_holders(ctx);
|
||||
let check_gil_refs = holders.check_gil_refs();
|
||||
let associated_method = quote! {
|
||||
#cfg_attrs
|
||||
unsafe fn #wrapper_ident(
|
||||
py: #pyo3_path::Python<'_>,
|
||||
_slf: *mut #pyo3_path::ffi::PyObject
|
||||
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
|
||||
#init_holders
|
||||
let result = #body;
|
||||
#check_gil_refs
|
||||
result
|
||||
}
|
||||
// Forward to `IntoPyCallbackOutput`, to handle `#[getter]`s returning results.
|
||||
PropertyType::Function {
|
||||
spec, self_type, ..
|
||||
} => {
|
||||
let wrapper_ident = format_ident!("__pymethod_get_{}__", spec.name);
|
||||
let call = impl_call_getter(cls, spec, self_type, &mut holders, ctx)?;
|
||||
let body = quote! {
|
||||
#pyo3_path::callback::convert(py, #call)
|
||||
};
|
||||
};
|
||||
|
||||
let init_holders = holders.init_holders(ctx);
|
||||
let associated_method = quote! {
|
||||
#cfg_attrs
|
||||
unsafe fn #wrapper_ident(
|
||||
py: #pyo3_path::Python<'_>,
|
||||
_slf: *mut #pyo3_path::ffi::PyObject
|
||||
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
|
||||
#init_holders
|
||||
let result = #body;
|
||||
result
|
||||
}
|
||||
};
|
||||
let method_def = quote! {
|
||||
#cfg_attrs
|
||||
#pyo3_path::class::PyMethodDefType::Getter(
|
||||
#pyo3_path::class::PyGetterDef::new(
|
||||
#python_name,
|
||||
#pyo3_path::impl_::pymethods::PyGetter(#cls::#wrapper_ident),
|
||||
#doc
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
let method_def = quote! {
|
||||
#cfg_attrs
|
||||
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
|
||||
#pyo3_path::class::PyMethodDefType::Getter(
|
||||
#pyo3_path::class::PyGetterDef::new(
|
||||
#python_name,
|
||||
#cls::#wrapper_ident,
|
||||
#doc
|
||||
)
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
Ok(MethodAndMethodDef {
|
||||
associated_method,
|
||||
method_def,
|
||||
})
|
||||
}
|
||||
}
|
||||
Ok(MethodAndMethodDef {
|
||||
associated_method,
|
||||
method_def,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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<&PyArg<'_>>, &[FnArg<'_>]) {
|
||||
fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&FnArg<'_>>, &[FnArg<'_>]) {
|
||||
match args {
|
||||
[FnArg::Py(py), args @ ..] => (Some(py), args),
|
||||
[py, args @ ..] if utils::is_python(py.ty) => (Some(py), args),
|
||||
args => (None, args),
|
||||
}
|
||||
}
|
||||
|
@ -864,7 +865,7 @@ pub enum PropertyType<'a> {
|
|||
}
|
||||
|
||||
impl PropertyType<'_> {
|
||||
fn null_terminated_python_name(&self, ctx: &Ctx) -> Result<LitCStr> {
|
||||
fn null_terminated_python_name(&self) -> Result<syn::LitStr> {
|
||||
match self {
|
||||
PropertyType::Descriptor {
|
||||
field,
|
||||
|
@ -879,23 +880,23 @@ impl PropertyType<'_> {
|
|||
if let Some(rule) = renaming_rule {
|
||||
name = utils::apply_renaming_rule(*rule, &name);
|
||||
}
|
||||
name.push('\0');
|
||||
name
|
||||
}
|
||||
(None, None) => {
|
||||
bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`");
|
||||
}
|
||||
};
|
||||
let name = CString::new(name).unwrap();
|
||||
Ok(LitCStr::new(name, field.span(), ctx))
|
||||
Ok(syn::LitStr::new(&name, field.span()))
|
||||
}
|
||||
PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name(ctx)),
|
||||
PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name()),
|
||||
}
|
||||
}
|
||||
|
||||
fn doc(&self, ctx: &Ctx) -> Cow<'_, PythonDoc> {
|
||||
fn doc(&self) -> Cow<'_, PythonDoc> {
|
||||
match self {
|
||||
PropertyType::Descriptor { field, .. } => {
|
||||
Cow::Owned(utils::get_doc(&field.attrs, None, ctx))
|
||||
Cow::Owned(utils::get_doc(&field.attrs, None))
|
||||
}
|
||||
PropertyType::Function { doc, .. } => Cow::Borrowed(doc),
|
||||
}
|
||||
|
@ -904,10 +905,10 @@ impl PropertyType<'_> {
|
|||
|
||||
const __STR__: SlotDef = SlotDef::new("Py_tp_str", "reprfunc");
|
||||
pub const __REPR__: SlotDef = SlotDef::new("Py_tp_repr", "reprfunc");
|
||||
pub const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc")
|
||||
const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc")
|
||||
.ret_ty(Ty::PyHashT)
|
||||
.return_conversion(TokenGenerator(
|
||||
|Ctx { pyo3_path, .. }: &Ctx| quote! { #pyo3_path::callback::HashCallbackOutput },
|
||||
|Ctx { pyo3_path }: &Ctx| quote! { #pyo3_path::callback::HashCallbackOutput },
|
||||
));
|
||||
pub const __RICHCMP__: SlotDef = SlotDef::new("Py_tp_richcompare", "richcmpfunc")
|
||||
.extract_error_mode(ExtractErrorMode::NotImplemented)
|
||||
|
@ -928,7 +929,7 @@ const __ANEXT__: SlotDef = SlotDef::new("Py_am_anext", "unaryfunc").return_speci
|
|||
),
|
||||
TokenGenerator(|_| quote! { async_iter_tag }),
|
||||
);
|
||||
pub const __LEN__: SlotDef = SlotDef::new("Py_mp_length", "lenfunc").ret_ty(Ty::PySsizeT);
|
||||
const __LEN__: SlotDef = SlotDef::new("Py_mp_length", "lenfunc").ret_ty(Ty::PySsizeT);
|
||||
const __CONTAINS__: SlotDef = SlotDef::new("Py_sq_contains", "objobjproc")
|
||||
.arguments(&[Ty::Object])
|
||||
.ret_ty(Ty::Int);
|
||||
|
@ -938,8 +939,7 @@ const __INPLACE_CONCAT__: SlotDef =
|
|||
SlotDef::new("Py_sq_concat", "binaryfunc").arguments(&[Ty::Object]);
|
||||
const __INPLACE_REPEAT__: SlotDef =
|
||||
SlotDef::new("Py_sq_repeat", "ssizeargfunc").arguments(&[Ty::PySsizeT]);
|
||||
pub const __GETITEM__: SlotDef =
|
||||
SlotDef::new("Py_mp_subscript", "binaryfunc").arguments(&[Ty::Object]);
|
||||
const __GETITEM__: SlotDef = SlotDef::new("Py_mp_subscript", "binaryfunc").arguments(&[Ty::Object]);
|
||||
|
||||
const __POS__: SlotDef = SlotDef::new("Py_nb_positive", "unaryfunc");
|
||||
const __NEG__: SlotDef = SlotDef::new("Py_nb_negative", "unaryfunc");
|
||||
|
@ -1030,11 +1030,7 @@ enum Ty {
|
|||
|
||||
impl Ty {
|
||||
fn ffi_type(self, ctx: &Ctx) -> TokenStream {
|
||||
let Ctx {
|
||||
pyo3_path,
|
||||
output_span,
|
||||
} = ctx;
|
||||
let pyo3_path = pyo3_path.to_tokens_spanned(*output_span);
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
match self {
|
||||
Ty::Object | Ty::MaybeNullObject => quote! { *mut #pyo3_path::ffi::PyObject },
|
||||
Ty::NonNullObject => quote! { ::std::ptr::NonNull<#pyo3_path::ffi::PyObject> },
|
||||
|
@ -1055,19 +1051,21 @@ impl Ty {
|
|||
holders: &mut Holders,
|
||||
ctx: &Ctx,
|
||||
) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let name_str = arg.name.unraw().to_string();
|
||||
match self {
|
||||
Ty::Object => extract_object(
|
||||
extract_error_mode,
|
||||
holders,
|
||||
arg,
|
||||
&name_str,
|
||||
quote! { #ident },
|
||||
arg.ty.span(),
|
||||
ctx
|
||||
),
|
||||
Ty::MaybeNullObject => extract_object(
|
||||
extract_error_mode,
|
||||
holders,
|
||||
arg,
|
||||
&name_str,
|
||||
quote! {
|
||||
if #ident.is_null() {
|
||||
#pyo3_path::ffi::Py_None()
|
||||
|
@ -1075,20 +1073,23 @@ impl Ty {
|
|||
#ident
|
||||
}
|
||||
},
|
||||
arg.ty.span(),
|
||||
ctx
|
||||
),
|
||||
Ty::NonNullObject => extract_object(
|
||||
extract_error_mode,
|
||||
holders,
|
||||
arg,
|
||||
&name_str,
|
||||
quote! { #ident.as_ptr() },
|
||||
arg.ty.span(),
|
||||
ctx
|
||||
),
|
||||
Ty::IPowModulo => extract_object(
|
||||
extract_error_mode,
|
||||
holders,
|
||||
arg,
|
||||
&name_str,
|
||||
quote! { #ident.as_ptr() },
|
||||
arg.ty.span(),
|
||||
ctx
|
||||
),
|
||||
Ty::CompareOp => extract_error_mode.handle_error(
|
||||
|
@ -1099,7 +1100,7 @@ impl Ty {
|
|||
ctx
|
||||
),
|
||||
Ty::PySsizeT => {
|
||||
let ty = arg.ty();
|
||||
let ty = arg.ty;
|
||||
extract_error_mode.handle_error(
|
||||
quote! {
|
||||
::std::convert::TryInto::<#ty>::try_into(#ident).map_err(|e| #pyo3_path::exceptions::PyValueError::new_err(e.to_string()))
|
||||
|
@ -1116,35 +1117,27 @@ impl Ty {
|
|||
fn extract_object(
|
||||
extract_error_mode: ExtractErrorMode,
|
||||
holders: &mut Holders,
|
||||
arg: &FnArg<'_>,
|
||||
name: &str,
|
||||
source_ptr: TokenStream,
|
||||
span: Span,
|
||||
ctx: &Ctx,
|
||||
) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let name = arg.name().unraw().to_string();
|
||||
|
||||
let extract =
|
||||
if let Some(from_py_with) = arg.from_py_with().map(|from_py_with| &from_py_with.value) {
|
||||
quote! {
|
||||
#pyo3_path::impl_::extract_argument::from_py_with(
|
||||
#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &#source_ptr).0,
|
||||
#name,
|
||||
#from_py_with as fn(_) -> _,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
let holder = holders.push_holder(Span::call_site());
|
||||
quote! {
|
||||
#pyo3_path::impl_::extract_argument::extract_argument(
|
||||
#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &#source_ptr).0,
|
||||
&mut #holder,
|
||||
#name
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let extracted = extract_error_mode.handle_error(extract, ctx);
|
||||
quote!(#extracted)
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let holder = holders.push_holder(Span::call_site());
|
||||
let gil_refs_checker = holders.push_gil_refs_checker(span);
|
||||
let extracted = extract_error_mode.handle_error(
|
||||
quote! {
|
||||
#pyo3_path::impl_::extract_argument::extract_argument(
|
||||
#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &#source_ptr).0,
|
||||
&mut #holder,
|
||||
#name
|
||||
)
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
quote! {
|
||||
#pyo3_path::impl_::deprecations::inspect_type(#extracted, &#gil_refs_checker)
|
||||
}
|
||||
}
|
||||
|
||||
enum ReturnMode {
|
||||
|
@ -1154,13 +1147,15 @@ enum ReturnMode {
|
|||
}
|
||||
|
||||
impl ReturnMode {
|
||||
fn return_call_output(&self, call: TokenStream, ctx: &Ctx) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
fn return_call_output(&self, call: TokenStream, ctx: &Ctx, holders: &Holders) -> TokenStream {
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let check_gil_refs = holders.check_gil_refs();
|
||||
match self {
|
||||
ReturnMode::Conversion(conversion) => {
|
||||
let conversion = TokenGeneratorCtx(*conversion, ctx);
|
||||
quote! {
|
||||
let _result: #pyo3_path::PyResult<#conversion> = #pyo3_path::callback::convert(py, #call);
|
||||
#check_gil_refs
|
||||
#pyo3_path::callback::convert(py, _result)
|
||||
}
|
||||
}
|
||||
|
@ -1170,12 +1165,14 @@ impl ReturnMode {
|
|||
quote! {
|
||||
let _result = #call;
|
||||
use #pyo3_path::impl_::pymethods::{#traits};
|
||||
#check_gil_refs
|
||||
(&_result).#tag().convert(py, _result)
|
||||
}
|
||||
}
|
||||
ReturnMode::ReturnSelf => quote! {
|
||||
let _result: #pyo3_path::PyResult<()> = #pyo3_path::callback::convert(py, #call);
|
||||
_result?;
|
||||
#check_gil_refs
|
||||
#pyo3_path::ffi::Py_XINCREF(_raw_slf);
|
||||
::std::result::Result::Ok(_raw_slf)
|
||||
},
|
||||
|
@ -1254,7 +1251,7 @@ impl SlotDef {
|
|||
method_name: &str,
|
||||
ctx: &Ctx,
|
||||
) -> Result<MethodAndSlotDef> {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let SlotDef {
|
||||
slot,
|
||||
func_ty,
|
||||
|
@ -1334,7 +1331,7 @@ fn generate_method_body(
|
|||
return_mode: Option<&ReturnMode>,
|
||||
ctx: &Ctx,
|
||||
) -> Result<TokenStream> {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let self_arg = spec
|
||||
.tp
|
||||
.self_arg(Some(cls), extract_error_mode, holders, ctx);
|
||||
|
@ -1342,10 +1339,12 @@ fn generate_method_body(
|
|||
let args = extract_proto_arguments(spec, arguments, extract_error_mode, holders, ctx)?;
|
||||
let call = quote! { #cls::#rust_name(#self_arg #(#args),*) };
|
||||
Ok(if let Some(return_mode) = return_mode {
|
||||
return_mode.return_call_output(call, ctx)
|
||||
return_mode.return_call_output(call, ctx, holders)
|
||||
} else {
|
||||
let check_gil_refs = holders.check_gil_refs();
|
||||
quote! {
|
||||
let result = #call;
|
||||
#check_gil_refs;
|
||||
#pyo3_path::callback::convert(py, result)
|
||||
}
|
||||
})
|
||||
|
@ -1384,7 +1383,7 @@ impl SlotFragmentDef {
|
|||
spec: &FnSpec<'_>,
|
||||
ctx: &Ctx,
|
||||
) -> Result<TokenStream> {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
let SlotFragmentDef {
|
||||
fragment,
|
||||
arguments,
|
||||
|
@ -1524,12 +1523,12 @@ fn extract_proto_arguments(
|
|||
let mut non_python_args = 0;
|
||||
|
||||
for arg in &spec.signature.arguments {
|
||||
if let FnArg::Py(..) = arg {
|
||||
if arg.py {
|
||||
args.push(quote! { py });
|
||||
} else {
|
||||
let ident = syn::Ident::new(&format!("arg{}", non_python_args), Span::call_site());
|
||||
let conversions = proto_args.get(non_python_args)
|
||||
.ok_or_else(|| err_spanned!(arg.ty().span() => format!("Expected at most {} non-python arguments", proto_args.len())))?
|
||||
.ok_or_else(|| err_spanned!(arg.ty.span() => format!("Expected at most {} non-python arguments", proto_args.len())))?
|
||||
.extract(&ident, arg, extract_error_mode, holders, ctx);
|
||||
non_python_args += 1;
|
||||
args.push(conversions);
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
use pyo3_build_config::PythonVersion;
|
||||
|
||||
pub const PY_3_9: PythonVersion = PythonVersion { major: 3, minor: 9 };
|
|
@ -1,31 +1,23 @@
|
|||
use crate::utils::Ctx;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, quote_spanned};
|
||||
use quote::quote;
|
||||
|
||||
pub(crate) fn some_wrap(obj: TokenStream, ctx: &Ctx) -> TokenStream {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
quote! {
|
||||
#pyo3_path::impl_::wrap::SomeWrap::wrap(#obj)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ok_wrap(obj: TokenStream, ctx: &Ctx) -> TokenStream {
|
||||
let Ctx {
|
||||
pyo3_path,
|
||||
output_span,
|
||||
} = ctx;
|
||||
let pyo3_path = pyo3_path.to_tokens_spanned(*output_span);
|
||||
quote_spanned! {*output_span=>
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
quote! {
|
||||
#pyo3_path::impl_::wrap::OkWrap::wrap(#obj)
|
||||
.map_err(::core::convert::Into::<#pyo3_path::PyErr>::into)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_result_into_ptr(result: TokenStream, ctx: &Ctx) -> TokenStream {
|
||||
let Ctx {
|
||||
pyo3_path,
|
||||
output_span,
|
||||
} = ctx;
|
||||
let pyo3_path = pyo3_path.to_tokens_spanned(*output_span);
|
||||
quote_spanned! {*output_span=> #pyo3_path::impl_::wrap::map_result_into_ptr(py, #result) }
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
quote! { #pyo3_path::impl_::wrap::map_result_into_ptr(py, #result) }
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use crate::attributes::{CrateAttribute, RenamingRule};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, ToTokens};
|
||||
use std::ffi::CString;
|
||||
use syn::spanned::Spanned;
|
||||
use quote::ToTokens;
|
||||
use syn::{punctuated::Punctuated, Token};
|
||||
|
||||
use crate::attributes::{CrateAttribute, RenamingRule};
|
||||
|
||||
/// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span.
|
||||
macro_rules! err_spanned {
|
||||
($span:expr => $msg:expr) => {
|
||||
|
@ -26,20 +25,7 @@ macro_rules! ensure_spanned {
|
|||
if !($condition) {
|
||||
bail_spanned!($span => $msg);
|
||||
}
|
||||
};
|
||||
($($condition:expr, $span:expr => $msg:expr;)*) => {
|
||||
if let Some(e) = [$(
|
||||
(!($condition)).then(|| err_spanned!($span => $msg)),
|
||||
)*]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.reduce(|mut acc, e| {
|
||||
acc.combine(e);
|
||||
acc
|
||||
}) {
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the given type `ty` is `pyo3::Python`.
|
||||
|
@ -68,71 +54,21 @@ pub fn option_type_argument(ty: &syn::Type) -> Option<&syn::Type> {
|
|||
None
|
||||
}
|
||||
|
||||
// TODO: Replace usage of this by [`syn::LitCStr`] when on MSRV 1.77
|
||||
#[derive(Clone)]
|
||||
pub struct LitCStr {
|
||||
lit: CString,
|
||||
span: Span,
|
||||
pyo3_path: PyO3CratePath,
|
||||
}
|
||||
|
||||
impl LitCStr {
|
||||
pub fn new(lit: CString, span: Span, ctx: &Ctx) -> Self {
|
||||
Self {
|
||||
lit,
|
||||
span,
|
||||
pyo3_path: ctx.pyo3_path.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty(ctx: &Ctx) -> Self {
|
||||
Self {
|
||||
lit: CString::new("").unwrap(),
|
||||
span: Span::call_site(),
|
||||
pyo3_path: ctx.pyo3_path.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl quote::ToTokens for LitCStr {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
if cfg!(c_str_lit) {
|
||||
syn::LitCStr::new(&self.lit, self.span).to_tokens(tokens);
|
||||
} else {
|
||||
let pyo3_path = &self.pyo3_path;
|
||||
let lit = self.lit.to_str().unwrap();
|
||||
tokens.extend(quote::quote_spanned!(self.span => #pyo3_path::ffi::c_str!(#lit)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A syntax tree which evaluates to a nul-terminated docstring for Python.
|
||||
///
|
||||
/// Typically the tokens will just be that string, but if the original docs included macro
|
||||
/// expressions then the tokens will be a concat!("...", "\n", "\0") expression of the strings and
|
||||
/// macro parts. contents such as parse the string contents.
|
||||
/// macro parts.
|
||||
/// contents such as parse the string contents.
|
||||
#[derive(Clone)]
|
||||
pub struct PythonDoc(PythonDocKind);
|
||||
|
||||
#[derive(Clone)]
|
||||
enum PythonDocKind {
|
||||
LitCStr(LitCStr),
|
||||
// There is currently no way to `concat!` c-string literals, we fallback to the `c_str!` macro in
|
||||
// this case.
|
||||
Tokens(TokenStream),
|
||||
}
|
||||
pub struct PythonDoc(TokenStream);
|
||||
|
||||
/// Collects all #[doc = "..."] attributes into a TokenStream evaluating to a null-terminated string.
|
||||
///
|
||||
/// If this doc is for a callable, the provided `text_signature` can be passed to prepend
|
||||
/// this to the documentation suitable for Python to extract this into the `__text_signature__`
|
||||
/// attribute.
|
||||
pub fn get_doc(
|
||||
attrs: &[syn::Attribute],
|
||||
mut text_signature: Option<String>,
|
||||
ctx: &Ctx,
|
||||
) -> PythonDoc {
|
||||
let Ctx { pyo3_path, .. } = ctx;
|
||||
pub fn get_doc(attrs: &[syn::Attribute], mut text_signature: Option<String>) -> PythonDoc {
|
||||
// insert special divider between `__text_signature__` and doc
|
||||
// (assume text_signature is itself well-formed)
|
||||
if let Some(text_signature) = &mut text_signature {
|
||||
|
@ -184,28 +120,20 @@ pub fn get_doc(
|
|||
syn::token::Bracket(Span::call_site()).surround(&mut tokens, |tokens| {
|
||||
parts.to_tokens(tokens);
|
||||
syn::token::Comma(Span::call_site()).to_tokens(tokens);
|
||||
syn::LitStr::new("\0", Span::call_site()).to_tokens(tokens);
|
||||
});
|
||||
|
||||
PythonDoc(PythonDocKind::Tokens(
|
||||
quote!(#pyo3_path::ffi::c_str!(#tokens)),
|
||||
))
|
||||
PythonDoc(tokens)
|
||||
} else {
|
||||
// Just a string doc - return directly with nul terminator
|
||||
let docs = CString::new(current_part).unwrap();
|
||||
PythonDoc(PythonDocKind::LitCStr(LitCStr::new(
|
||||
docs,
|
||||
Span::call_site(),
|
||||
ctx,
|
||||
)))
|
||||
current_part.push('\0');
|
||||
PythonDoc(current_part.to_token_stream())
|
||||
}
|
||||
}
|
||||
|
||||
impl quote::ToTokens for PythonDoc {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match &self.0 {
|
||||
PythonDocKind::LitCStr(lit) => lit.to_tokens(tokens),
|
||||
PythonDocKind::Tokens(toks) => toks.to_tokens(tokens),
|
||||
}
|
||||
self.0.to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,39 +145,20 @@ pub fn unwrap_ty_group(mut ty: &syn::Type) -> &syn::Type {
|
|||
}
|
||||
|
||||
pub struct Ctx {
|
||||
/// Where we can find the pyo3 crate
|
||||
pub pyo3_path: PyO3CratePath,
|
||||
|
||||
/// If we are in a pymethod or pyfunction,
|
||||
/// this will be the span of the return type
|
||||
pub output_span: Span,
|
||||
}
|
||||
|
||||
impl Ctx {
|
||||
pub(crate) fn new(attr: &Option<CrateAttribute>, signature: Option<&syn::Signature>) -> Self {
|
||||
pub(crate) fn new(attr: &Option<CrateAttribute>) -> Self {
|
||||
let pyo3_path = match attr {
|
||||
Some(attr) => PyO3CratePath::Given(attr.value.0.clone()),
|
||||
None => PyO3CratePath::Default,
|
||||
};
|
||||
|
||||
let output_span = if let Some(syn::Signature {
|
||||
output: syn::ReturnType::Type(_, output_type),
|
||||
..
|
||||
}) = &signature
|
||||
{
|
||||
output_type.span()
|
||||
} else {
|
||||
Span::call_site()
|
||||
};
|
||||
|
||||
Self {
|
||||
pyo3_path,
|
||||
output_span,
|
||||
}
|
||||
Self { pyo3_path }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum PyO3CratePath {
|
||||
Given(syn::Path),
|
||||
Default,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue