Merge branch 'main' of https://github.com/PyO3/pyo3 into enhanced_extract_type_errors
This commit is contained in:
commit
403d882d6c
|
@ -0,0 +1,97 @@
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
name: Benchmark
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
name: Cargo benchmark
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: cargo-${{ runner.os }}-bench-${{ hashFiles('**/Cargo.toml') }}
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
for bench in call dict gil list pyclass pyobject set tuple; do
|
||||
cargo bench --features hashbrown --bench "bench_$bench" -- --output-format bencher | tee -a output.txt
|
||||
done
|
||||
|
||||
# Download previous benchmark result from cache (if exists)
|
||||
- name: Download previous benchmark data
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ./cache
|
||||
key: ${{ runner.os }}-benchmark
|
||||
|
||||
# Run `github-action-benchmark` action
|
||||
- name: Store benchmark result
|
||||
uses: rhysd/github-action-benchmark@v1
|
||||
with:
|
||||
name: pyo3-bench
|
||||
# What benchmark tool the output.txt came from
|
||||
tool: "cargo"
|
||||
# Where the output from the benchmark tool is stored
|
||||
output-file-path: output.txt
|
||||
# GitHub API token to make a commit comment
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto-push: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
pytest-benchmark:
|
||||
name: pytest benchmark
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: cargo-${{ runner.os }}-pytest-bench-${{ hashFiles('**/Cargo.toml') }}
|
||||
continue-on-error: true
|
||||
|
||||
- name: Download previous benchmark data
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ./cache
|
||||
key: ${{ runner.os }}-pytest-benchmark
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
cd examples/pyo3-benchmarks
|
||||
python -m pip install -r requirements-dev.txt
|
||||
python setup.py develop
|
||||
pytest --benchmark-json ../../output.json
|
||||
- name: Store benchmark result
|
||||
uses: rhysd/github-action-benchmark@v1
|
||||
with:
|
||||
name: pytest-bench
|
||||
tool: "pytest"
|
||||
output-file-path: output.json
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto-push: ${{ github.event_name != 'pull_request' }}
|
|
@ -42,21 +42,33 @@ jobs:
|
|||
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} ${{ matrix.msrv }}
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
strategy:
|
||||
fail-fast: false # If one platform fails, allow the rest to keep testing.
|
||||
fail-fast: false # If one platform fails, allow the rest to keep testing.
|
||||
matrix:
|
||||
rust: [stable]
|
||||
# Github actions has a "bogus" 3.10-beta.1 release which interacts badly
|
||||
# with pytest (use 3.10-dev again once pytest has released a fix):
|
||||
# https://github.com/actions/setup-python/issues/207
|
||||
# https://github.com/pytest-dev/pytest/issues/8539
|
||||
# python-version: [3.6, 3.7, 3.8, 3.9, 3.10-dev, pypy-3.6, pypy-3.7]
|
||||
python-version: [3.6, 3.7, 3.8, 3.9, 3.10.0-alpha.7, pypy-3.6, pypy-3.7]
|
||||
platform: [
|
||||
{ os: "macos-latest", python-architecture: "x64", rust-target: "x86_64-apple-darwin" },
|
||||
{ os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu" },
|
||||
{ os: "windows-latest", python-architecture: "x64", rust-target: "x86_64-pc-windows-msvc" },
|
||||
{ os: "windows-latest", python-architecture: "x86", rust-target: "i686-pc-windows-msvc" },
|
||||
]
|
||||
python-version: [3.6, 3.7, 3.8, 3.9, 3.10-dev, pypy-3.6, pypy-3.7]
|
||||
platform:
|
||||
[
|
||||
{
|
||||
os: "macos-latest",
|
||||
python-architecture: "x64",
|
||||
rust-target: "x86_64-apple-darwin",
|
||||
},
|
||||
{
|
||||
os: "ubuntu-latest",
|
||||
python-architecture: "x64",
|
||||
rust-target: "x86_64-unknown-linux-gnu",
|
||||
},
|
||||
{
|
||||
os: "windows-latest",
|
||||
python-architecture: "x64",
|
||||
rust-target: "x86_64-pc-windows-msvc",
|
||||
},
|
||||
{
|
||||
os: "windows-latest",
|
||||
python-architecture: "x86",
|
||||
rust-target: "i686-pc-windows-msvc",
|
||||
},
|
||||
]
|
||||
exclude:
|
||||
# There is no 64-bit pypy on windows for pypy-3.6
|
||||
- python-version: pypy-3.6
|
||||
|
@ -68,7 +80,12 @@ jobs:
|
|||
# Test minimal supported Rust version
|
||||
- rust: 1.41.1
|
||||
python-version: 3.9
|
||||
platform: { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu" }
|
||||
platform:
|
||||
{
|
||||
os: "ubuntu-latest",
|
||||
python-architecture: "x64",
|
||||
rust-target: "x86_64-unknown-linux-gnu",
|
||||
}
|
||||
msrv: "MSRV"
|
||||
|
||||
steps:
|
||||
|
@ -113,11 +130,13 @@ jobs:
|
|||
id: settings
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::set-output name=all_additive_features::macros num-bigint num-complex hashbrown serde multiple-pymethods"
|
||||
echo "::set-output name=all_additive_features::macros num-bigint num-complex hashbrown indexmap serde multiple-pymethods"
|
||||
|
||||
- if: matrix.msrv == 'MSRV'
|
||||
name: Prepare minimal package versions (MSRV only)
|
||||
run: cargo update -p hashbrown --precise 0.9.1
|
||||
run: |
|
||||
cargo update -p indexmap --precise 1.6.2
|
||||
cargo update -p hashbrown:0.11.2 --precise 0.9.1
|
||||
|
||||
- name: Build docs
|
||||
run: cargo doc --no-deps --no-default-features --features "${{ steps.settings.outputs.all_additive_features }}"
|
||||
|
@ -151,6 +170,9 @@ jobs:
|
|||
- name: Test proc-macro code
|
||||
run: cargo test --manifest-path=pyo3-macros-backend/Cargo.toml
|
||||
|
||||
- name: Test build config
|
||||
run: cargo test --manifest-path=pyo3-build-config/Cargo.toml
|
||||
|
||||
- name: Install python test dependencies
|
||||
run: python -m pip install -U pip tox
|
||||
|
||||
|
@ -163,6 +185,16 @@ jobs:
|
|||
env:
|
||||
TOX_TESTENV_PASSENV: "CARGO_BUILD_TARGET"
|
||||
|
||||
- name: Test cross compilation
|
||||
if: ${{ matrix.platform.os == 'ubuntu-latest' && matrix.python-version == '3.9' }}
|
||||
uses: messense/maturin-action@v1
|
||||
env:
|
||||
PYO3_CROSS_LIB_DIR: /opt/python/cp39-cp39/lib
|
||||
with:
|
||||
target: aarch64-unknown-linux-gnu
|
||||
manylinux: auto
|
||||
args: --release -i python3.9 --no-sdist -m examples/maturin-starter/Cargo.toml
|
||||
|
||||
env:
|
||||
CARGO_TERM_VERBOSE: true
|
||||
CARGO_BUILD_TARGET: ${{ matrix.platform.rust-target }}
|
||||
|
@ -198,8 +230,10 @@ jobs:
|
|||
override: true
|
||||
profile: minimal
|
||||
components: llvm-tools-preview
|
||||
- run: LLVM_PROFILE_FILE="coverage-%p-%m.profraw" cargo test --no-default-features --no-fail-fast
|
||||
- run: LLVM_PROFILE_FILE="coverage-features-%p-%m.profraw" cargo test --no-default-features --no-fail-fast --features "macros num-bigint num-complex hashbrown serde multiple-pymethods"
|
||||
- run: cargo test --no-default-features --no-fail-fast
|
||||
- run: cargo test --no-default-features --no-fail-fast --features "macros num-bigint num-complex hashbrown indexmap serde multiple-pymethods"
|
||||
- run: cargo test --manifest-path=pyo3-macros-backend/Cargo.toml
|
||||
- run: cargo test --manifest-path=pyo3-build-config/Cargo.toml
|
||||
# can't yet use actions-rs/grcov with source-based coverage: https://github.com/actions-rs/grcov/issues/105
|
||||
# - uses: actions-rs/grcov@v0.1
|
||||
# id: coverage
|
||||
|
@ -215,3 +249,4 @@ jobs:
|
|||
CARGO_TERM_VERBOSE: true
|
||||
RUSTFLAGS: "-Zinstrument-coverage"
|
||||
RUSTDOCFLAGS: "-Zinstrument-coverage"
|
||||
LLVM_PROFILE_FILE: "coverage-%p-%m.profraw"
|
||||
|
|
|
@ -21,13 +21,13 @@ jobs:
|
|||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
profile: minimal
|
||||
|
||||
- name: Setup mdBook
|
||||
uses: peaceiris/actions-mdbook@v1
|
||||
with:
|
||||
mdbook-version: '0.4.7'
|
||||
mdbook-version: "0.4.10"
|
||||
|
||||
- name: Prepare tag
|
||||
id: prepare_tag
|
||||
|
@ -44,7 +44,7 @@ jobs:
|
|||
# This adds the docs to gh-pages-build/doc
|
||||
- name: Build the doc
|
||||
run: |
|
||||
cargo +nightly rustdoc --lib --no-default-features --features="macros num-bigint num-complex hashbrown serde multiple-pymethods" -- --cfg docsrs
|
||||
cargo +nightly rustdoc --lib --no-default-features --features="macros num-bigint num-complex hashbrown indexmap serde multiple-pymethods" -- --cfg docsrs
|
||||
cp -r target/doc gh-pages-build/doc
|
||||
echo "<meta http-equiv=refresh content=0;url=pyo3/index.html>" > gh-pages-build/doc/index.html
|
||||
|
||||
|
@ -55,24 +55,25 @@ jobs:
|
|||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./gh-pages-build/
|
||||
destination_dir: ${{ steps.prepare_tag.outputs.tag_name }}
|
||||
full_commit_message: 'Upload documentation for ${{ steps.prepare_tag.outputs.tag_name }}'
|
||||
full_commit_message: "Upload documentation for ${{ steps.prepare_tag.outputs.tag_name }}"
|
||||
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
steps:
|
||||
- name: Create latest tag redirect
|
||||
- name: Create latest tag redirects
|
||||
env:
|
||||
TAG_NAME: ${{ needs.build.outputs.tag_name }}
|
||||
run: |
|
||||
mkdir public
|
||||
echo "<meta http-equiv=refresh content=0;url='$TAG_NAME/'>" > public/index.html
|
||||
ln -sfT $TAG_NAME public/latest
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3.7.0-8
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./public/
|
||||
full_commit_message: 'Release ${{ needs.build.outputs.tag_name }}'
|
||||
full_commit_message: "Release ${{ needs.build.outputs.tag_name }}"
|
||||
keep_files: true
|
||||
|
|
|
@ -17,20 +17,22 @@ protocol traits (e.g., `PyIterProtocol`) for supporting object protocols (i.e.,
|
|||
Since implementing `PyClass` requires lots of boilerplate, we have a proc-macro `#[pyclass]`.
|
||||
|
||||
To summarize, there are six main parts to the PyO3 codebase.
|
||||
|
||||
1. [Low-level bindings of Python/C API.](#1-low-level-bindings-of-python-capi)
|
||||
- [`src/ffi`]
|
||||
- [`src/ffi`]
|
||||
2. [Bindings to Python objects.](#2-bindings-to-python-objects)
|
||||
- [`src/instance.rs`] and [`src/types`]
|
||||
- [`src/instance.rs`] and [`src/types`]
|
||||
3. [`PyClass` and related functionalities.](#3-pyclass-and-related-functionalities)
|
||||
- [`src/pycell.rs`], [`src/pyclass.rs`], and more
|
||||
- [`src/pycell.rs`], [`src/pyclass.rs`], and more
|
||||
4. [Protocol methods like `__getitem__`.](#4-protocol-methods)
|
||||
- [`src/class`]
|
||||
- [`src/class`]
|
||||
5. [Procedural macros to simplify usage for users.](#5-procedural-macros-to-simplify-usage-for-users)
|
||||
- [`src/derive_utils.rs`], [`pyo3-macros`] and [`pyo3-macros-backend`]
|
||||
- [`src/derive_utils.rs`], [`pyo3-macros`] and [`pyo3-macros-backend`]
|
||||
6. [`build.rs`](#6-buildrs)
|
||||
- [`build.rs`](https://github.com/PyO3/pyo3/tree/master/build.rs)
|
||||
- [`build.rs`](https://github.com/PyO3/pyo3/tree/master/build.rs)
|
||||
|
||||
## 1. Low-level bindings of Python/C API
|
||||
|
||||
[`src/ffi`] contains wrappers of [Python/C API].
|
||||
|
||||
We aim to provide straight-forward Rust wrappers resembling the file structure of
|
||||
|
@ -44,17 +46,19 @@ In the [`src/ffi`] module, there is lots of conditional compilation such as `#[c
|
|||
`#[cfg(Py_37)]`, and `#[cfg(PyPy)]`.
|
||||
`Py_LIMITED_API` corresponds to `#define Py_LIMITED_API` macro in Python/C API.
|
||||
With `Py_LIMITED_API`, we can build a Python-version-agnostic binary called an
|
||||
[abi3 wheel](https://pyo3.rs/v0.13.2/building_and_distribution.html#py_limited_apiabi3).
|
||||
[abi3 wheel](https://pyo3.rs/latest/building_and_distribution.html#py_limited_apiabi3).
|
||||
`Py_37` means that the API is available from Python >= 3.7.
|
||||
There are also `Py_38`, `Py_39`, and so on.
|
||||
`PyPy` means that the API definition is for PyPy.
|
||||
Those flags are set in [`build.rs`](#6-buildrs).
|
||||
|
||||
## 2. Bindings to Python objects
|
||||
|
||||
[`src/types`] contains bindings to [built-in types](https://docs.python.org/3/library/stdtypes.html)
|
||||
of Python, such as `dict` and `list`.
|
||||
For historical reasons, Python's `object` is called `PyAny` in PyO3 and located in [`src/types/any.rs`].
|
||||
Currently, `PyAny` is a straightforward wrapper of `ffi::PyObject`, defined as:
|
||||
|
||||
```rust
|
||||
#[repr(transparent)]
|
||||
pub struct PyAny(UnsafeCell<ffi::PyObject>);
|
||||
|
@ -62,6 +66,7 @@ pub struct PyAny(UnsafeCell<ffi::PyObject>);
|
|||
|
||||
All built-in types are defined as a C struct.
|
||||
For example, `dict` is defined as:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
/* Base object */
|
||||
|
@ -77,6 +82,7 @@ typedef struct {
|
|||
|
||||
However, we cannot access such a specific data structure with `#[cfg(Py_LIMITED_API)]` set.
|
||||
Thus, all builtin objects are implemented as opaque types by wrapping `PyAny`, e.g.,:
|
||||
|
||||
```rust
|
||||
#[repr(transparent)]
|
||||
pub struct PyDict(PyAny);
|
||||
|
@ -92,12 +98,14 @@ Since we need lots of boilerplate for implementing common traits for these types
|
|||
[`src/types/mod.rs`].
|
||||
|
||||
## 3. `PyClass` and related functionalities
|
||||
|
||||
[`src/pycell.rs`], [`src/pyclass.rs`], and [`src/type_object.rs`] contain types and
|
||||
traits to make `#[pyclass]` work.
|
||||
Also, [`src/pyclass_init.rs`] and [`src/pyclass_slots.rs`] have related functionalities.
|
||||
|
||||
To realize object-oriented programming in C, all Python objects must have the following two fields
|
||||
at the beginning.
|
||||
|
||||
```rust
|
||||
#[repr(C)]
|
||||
pub struct PyObject {
|
||||
|
@ -106,9 +114,11 @@ pub struct PyObject {
|
|||
...
|
||||
}
|
||||
```
|
||||
|
||||
Thanks to this guarantee, casting `*mut A` to `*mut PyObject` is valid if `A` is a Python object.
|
||||
|
||||
To ensure this guarantee, we have a wrapper struct `PyCell<T>` in [`src/pycell.rs`] which is roughly:
|
||||
|
||||
```rust
|
||||
#[repr(C)]
|
||||
pub struct PyCell<T: PyClass> {
|
||||
|
@ -116,6 +126,7 @@ pub struct PyCell<T: PyClass> {
|
|||
inner: T,
|
||||
}
|
||||
```
|
||||
|
||||
Thus, when copying a Rust struct to a Python object, we first allocate `PyCell` on the Python heap and then
|
||||
move `T` into it.
|
||||
Also, `PyCell` provides [RefCell](https://doc.rust-lang.org/std/cell/struct.RefCell.html)-like methods
|
||||
|
@ -131,6 +142,7 @@ For example, you can see `type({})` shows `dict` and `type(type({}))` shows `typ
|
|||
`T: PyTypeObject` implies that `T` has a corresponding type object.
|
||||
|
||||
## 4. Protocol methods
|
||||
|
||||
Python has some built-in special methods called dunder, such as `__iter__`.
|
||||
They are called [abstract objects layer](https://docs.python.org/3/c-api/abstract.html) in
|
||||
Python/C API.
|
||||
|
@ -147,6 +159,7 @@ Also, [`src/class/methods.rs`] has utilities for `#[pyfunction]` and [`src/class
|
|||
some internal tricks for making `#[pyproto]` flexible.
|
||||
|
||||
## 5. Procedural macros to simplify usage for users.
|
||||
|
||||
[`pyo3-macros`] provides six proc-macro APIs: `pymodule`, `pyproto`, `pyfunction`, `pyclass`,
|
||||
`pymethods`, and `#[derive(FromPyObject)]`.
|
||||
[`pyo3-macros-backend`] has the actual implementations of these APIs.
|
||||
|
@ -154,33 +167,42 @@ some internal tricks for making `#[pyproto]` flexible.
|
|||
such as parsing function arguments.
|
||||
|
||||
## 6. `build.rs`
|
||||
|
||||
PyO3's [`build.rs`](https://github.com/PyO3/pyo3/tree/master/build.rs) is relatively long
|
||||
(about 900 lines) to support multiple architectures, interpreters, and usages.
|
||||
Below is a non-exhaustive list of its functionality:
|
||||
|
||||
- Cross-compiling support.
|
||||
- If `TARGET` architecture and `HOST` architecture differ, we find cross compile information
|
||||
from environment variables (`PYO3_CROSS_LIB_DIR` and `PYO3_CROSS_PYTHON_VERSION`) or system files.
|
||||
from environment variables (`PYO3_CROSS_LIB_DIR` and `PYO3_CROSS_PYTHON_VERSION`) or system files.
|
||||
- Find the interpreter for build and detect the Python version.
|
||||
- We have to set some version flags like `Py_37`.
|
||||
- If the interpreter is PyPy, we set `PyPy`.
|
||||
- If `PYO3_NO_PYTHON` environment variable is set then the interpreter detection is bypassed
|
||||
entirely and only abi3 extensions can be built.
|
||||
entirely and only abi3 extensions can be built.
|
||||
- Check if we are building a Python extension.
|
||||
- If we are building an extension (e.g., Python library installable by `pip`),
|
||||
we don't link `libpython`.
|
||||
Currently we use the `extension-module` feature for this purpose. This may change in the future.
|
||||
See [#1123](https://github.com/PyO3/pyo3/pull/1123).
|
||||
we don't link `libpython`.
|
||||
Currently we use the `extension-module` feature for this purpose. This may change in the future.
|
||||
See [#1123](https://github.com/PyO3/pyo3/pull/1123).
|
||||
|
||||
<!-- External Links -->
|
||||
[Python/C API]: https://docs.python.org/3/c-api/
|
||||
|
||||
[python/c api]: https://docs.python.org/3/c-api/
|
||||
|
||||
<!-- Crates -->
|
||||
|
||||
[`pyo3-macros`]: https://github.com/PyO3/pyo3/tree/master/pyo3-macros
|
||||
[`pyo3-macros-backend`]: https://github.com/PyO3/pyo3/tree/master/pyo3-macros-backend
|
||||
|
||||
<!-- Directories -->
|
||||
|
||||
[`src/class`]: https://github.com/PyO3/pyo3/tree/master/src/class
|
||||
[`src/ffi`]: https://github.com/PyO3/pyo3/tree/master/src/ffi
|
||||
[`src/types`]: https://github.com/PyO3/pyo3/tree/master/src/types
|
||||
|
||||
<!-- Files -->
|
||||
|
||||
[`src/derive_utils.rs`]: https://github.com/PyO3/pyo3/tree/master/src/derive_utils.rs
|
||||
[`src/instance.rs`]: https://github.com/PyO3/pyo3/tree/master/src/instance.rs
|
||||
[`src/pycell.rs`]: https://github.com/PyO3/pyo3/tree/master/src/pycell.rs
|
||||
|
|
252
CHANGELOG.md
252
CHANGELOG.md
|
@ -1,40 +1,78 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. For help with updating to new
|
||||
PyO3 versions, please see the [migration guide](https://pyo3.rs/main/migration.html).
|
||||
PyO3 versions, please see the [migration guide](https://pyo3.rs/latest/migration.html).
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Add `indexmap` feature to add `ToPyObject`, `IntoPy` and `FromPyObject` implementations for `indexmap::IndexMap`. [#1728](https://github.com/PyO3/pyo3/pull/1728)
|
||||
|
||||
### Changed
|
||||
|
||||
- `PyErr::new` no longer acquires the Python GIL internally. [#1724](https://github.com/PyO3/pyo3/pull/1724)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix regression in 0.14.0 rejecting usage of `#[doc(hidden)]` on structs and functions annotated with PyO3 macros. [#1722](https://github.com/PyO3/pyo3/pull/1722)
|
||||
- Fix regression in 0.14.0 leading to incorrect code coverage being computed for `#[pyfunction]`s. [#1726](https://github.com/PyO3/pyo3/pull/1726)
|
||||
- Fix incorrect FFI definition of `Py_Buffer` on PyPy. [#1737](https://github.com/PyO3/pyo3/pull/1737)
|
||||
- Fix incorrect calculation of `dictoffset` on 32-bit Windows. [#1475](https://github.com/PyO3/pyo3/pull/1475)
|
||||
|
||||
## [0.14.1] - 2021-07-04
|
||||
|
||||
### Added
|
||||
|
||||
- Implement `IntoPy<PyObject>` for `&PathBuf` and `&OsString`. [#1712](https://github.com/PyO3/pyo3/pull/1712)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix crashes on PyPy due to incorrect definitions of `PyList_SET_ITEM`. [#1713](https://github.com/PyO3/pyo3/pull/1713)
|
||||
|
||||
## [0.14.0] - 2021-07-03
|
||||
|
||||
### Packaging
|
||||
|
||||
- Update `num-bigint` optional dependency to 0.4. [#1481](https://github.com/PyO3/pyo3/pull/1481)
|
||||
- Update `num-complex` optional dependency to 0.4. [#1482](https://github.com/PyO3/pyo3/pull/1482)
|
||||
- Extend `hashbrown` optional dependency supported versions to include 0.11. [#1496](https://github.com/PyO3/pyo3/pull/1496)
|
||||
- Support PyPy 3.7. [#1538](https://github.com/PyO3/pyo3/pull/1538)
|
||||
|
||||
### Added
|
||||
- Add conversions for `[T; N]` for all `N` on Rust 1.51 and up. [#1128](https://github.com/PyO3/pyo3/pull/1128)
|
||||
- Add conversions between `OsStr`/`OsString`/`Path`/`PathBuf` and Python strings. [#1379](https://github.com/PyO3/pyo3/pull/1379)
|
||||
- Add `#[pyo3(from_py_with = "...")]` attribute for function arguments and struct fields to override the default from-Python conversion. [#1411](https://github.com/PyO3/pyo3/pull/1411)
|
||||
|
||||
- Extend conversions for `[T; N]` to all `N` using const generics (on Rust 1.51 and up). [#1128](https://github.com/PyO3/pyo3/pull/1128)
|
||||
- Add conversions between `OsStr`/ `OsString` and Python strings. [#1379](https://github.com/PyO3/pyo3/pull/1379)
|
||||
- Add conversions between `Path`/ `PathBuf` and Python strings (and `pathlib.Path` objects). [#1379](https://github.com/PyO3/pyo3/pull/1379) [#1654](https://github.com/PyO3/pyo3/pull/1654)
|
||||
- Add a new set of `#[pyo3(...)]` attributes to control various PyO3 macro functionality:
|
||||
- `#[pyo3(from_py_with = "...")]` function arguments and struct fields to override the default from-Python conversion. [#1411](https://github.com/PyO3/pyo3/pull/1411)
|
||||
- `#[pyo3(name = "...")]` for setting Python names. [#1567](https://github.com/PyO3/pyo3/pull/1567)
|
||||
- `#[pyo3(text_signature = "...")]` for setting text signature. [#1658](https://github.com/PyO3/pyo3/pull/1658)
|
||||
- Add FFI definition `PyCFunction_CheckExact` for Python 3.9 and later. [#1425](https://github.com/PyO3/pyo3/pull/1425)
|
||||
- Add FFI definition `Py_IS_TYPE`. [#1429](https://github.com/PyO3/pyo3/pull/1429)
|
||||
- Add FFI definition `_Py_InitializeMain`. [#1473](https://github.com/PyO3/pyo3/pull/1473)
|
||||
- Add FFI definitions from `cpython/import.h`.[#1475](https://github.com/PyO3/pyo3/pull/1475)
|
||||
- Add tuple and unit struct support for `#[pyclass]` macro. [#1504](https://github.com/PyO3/pyo3/pull/1504)
|
||||
- Add `#[pyo3(name = "...")]` syntax for setting Python names. [#1567](https://github.com/PyO3/pyo3/pull/1567)
|
||||
- Add FFI definition `PyDateTime_TimeZone_UTC`. [#1572](https://github.com/PyO3/pyo3/pull/1572)
|
||||
- Add support for `#[pyclass(extends=Exception)]`. [#1591](https://github.com/PyO3/pyo3/pull/1591)
|
||||
- Add `PyErr::cause` and `PyErr::set_cause`. [#1679](https://github.com/PyO3/pyo3/pull/1679)
|
||||
- Add FFI definitions from `cpython/pystate.h`. [#1687](https://github.com/PyO3/pyo3/pull/1687/)
|
||||
- Add `wrap_pyfunction!` macro to `pyo3::prelude`. [#1695](https://github.com/PyO3/pyo3/pull/1695)
|
||||
|
||||
### Changed
|
||||
- Allow only one `#[pymethods]` block per `#[pyclass]` by default, to simplify the proc macro implementations. Add `multiple-pymethods` feature to opt-in to the more complex full behavior. [#1457](https://github.com/PyO3/pyo3/pull/1457)
|
||||
- Change `PyTimeAcces::get_fold()` to return a `bool` instead of a `u8`. [#1397](https://github.com/PyO3/pyo3/pull/1397)
|
||||
- Deprecate FFI definition `PyCFunction_Call` for Python 3.9 and later. [#1425](https://github.com/PyO3/pyo3/pull/1425)
|
||||
- Deprecate FFI definitions `PyModule_GetFilename`. [#1425](https://github.com/PyO3/pyo3/pull/1425)
|
||||
|
||||
- Allow only one `#[pymethods]` block per `#[pyclass]` by default, to remove the dependency on `inventory`. Add a `multiple-pymethods` feature to opt-in the original behavior and dependency on `inventory`. [#1457](https://github.com/PyO3/pyo3/pull/1457)
|
||||
- Change `PyTimeAccess::get_fold` to return a `bool` instead of a `u8`. [#1397](https://github.com/PyO3/pyo3/pull/1397)
|
||||
- Deprecate FFI definition `PyCFunction_Call` for Python 3.9 and up. [#1425](https://github.com/PyO3/pyo3/pull/1425)
|
||||
- Deprecate FFI definition `PyModule_GetFilename`. [#1425](https://github.com/PyO3/pyo3/pull/1425)
|
||||
- The `auto-initialize` feature is no longer enabled by default. [#1443](https://github.com/PyO3/pyo3/pull/1443)
|
||||
- Change `PyCFunction::new()` and `PyCFunction::new_with_keywords()` to take `&'static str` arguments rather than implicitly copying (and leaking) them. [#1450](https://github.com/PyO3/pyo3/pull/1450)
|
||||
- Deprecate `PyModule` methods `call`, `call0`, `call1` and `get`. [#1492](https://github.com/PyO3/pyo3/pull/1492)
|
||||
- Deprecate `PyModule::call`, `PyModule::call0`, `PyModule::call1` and `PyModule::get`. [#1492](https://github.com/PyO3/pyo3/pull/1492)
|
||||
- Add length information to `PyBufferError`s raised from `PyBuffer::copy_to_slice` and `PyBuffer::copy_from_slice`. [#1534](https://github.com/PyO3/pyo3/pull/1534)
|
||||
- Automatically provide `-undefined` and `dynamic_lookup` linker arguments on macOS with `extension-module` feature. [#1539](https://github.com/PyO3/pyo3/pull/1539)
|
||||
- Automatically set `-undefined` and `dynamic_lookup` linker arguments on macOS with the `extension-module` feature. [#1539](https://github.com/PyO3/pyo3/pull/1539)
|
||||
- Deprecate `#[pyproto]` methods which are easier to implement as `#[pymethods]`: [#1560](https://github.com/PyO3/pyo3/pull/1560)
|
||||
- `PyBasicProtocol::__bytes__` and `PyBasicProtocol::__format__`
|
||||
- `PyContextProtocol::__enter__` and `PyContextProtocol::__exit__`
|
||||
|
@ -42,56 +80,76 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- `PyMappingProtocol::__reversed__`
|
||||
- `PyNumberProtocol::__complex__` and `PyNumberProtocol::__round__`
|
||||
- `PyAsyncProtocol::__aenter__` and `PyAsyncProtocol::__aexit__`
|
||||
- Deprecate `#[name = "..."]` attributes in favor of `#[pyo3(name = "...")]`. [#1567](https://github.com/PyO3/pyo3/pull/1567)
|
||||
- Deprecate several attributes in favor of the new `#[pyo3(...)]` options:
|
||||
- `#[name = "..."]`, replaced by `#[pyo3(name = "...")]` [#1567](https://github.com/PyO3/pyo3/pull/1567)
|
||||
- `#[pyfn(m, "name")]`, replaced by `#[pyfn(m)] #[pyo3(name = "...")]`. [#1610](https://github.com/PyO3/pyo3/pull/1610)
|
||||
- `#[pymodule(name)]`, replaced by `#[pymodule] #[pyo3(name = "...")]` [#1650](https://github.com/PyO3/pyo3/pull/1650)
|
||||
- `#[text_signature = "..."]`, replaced by `#[pyo3(text_signature = "...")]`. [#1658](https://github.com/PyO3/pyo3/pull/1658)
|
||||
- Reduce LLVM line counts to improve compilation times. [#1604](https://github.com/PyO3/pyo3/pull/1604)
|
||||
- Deprecate string-literal second argument to `#[pyfn(m, "name")]`. [#1610](https://github.com/PyO3/pyo3/pull/1610)
|
||||
- No longer call `PyEval_InitThreads()` in `#[pymodule]` init code. [#1630](https://github.com/PyO3/pyo3/pull/1630)
|
||||
- No longer call `PyEval_InitThreads` in `#[pymodule]` init code. [#1630](https://github.com/PyO3/pyo3/pull/1630)
|
||||
- Use `METH_FASTCALL` argument passing convention, when possible, to improve `#[pyfunction]` and method performance.
|
||||
[#1619](https://github.com/PyO3/pyo3/pull/1619), [#1660](https://github.com/PyO3/pyo3/pull/1660)
|
||||
- Filter sysconfigdata candidates by architecture when cross-compiling. [#1626](https://github.com/PyO3/pyo3/pull/1626)
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove deprecated exception names `BaseException` etc. [#1426](https://github.com/PyO3/pyo3/pull/1426)
|
||||
- Remove deprecated redundant methods `Python::is_instance`, `Python::is_subclass`, `Python::release`, `Python::xdecref`, and `Py::from_owned_ptr_or_panic`. [#1426](https://github.com/PyO3/pyo3/pull/1426)
|
||||
- Remove many ffi definitions which never existed in the Python C-API:
|
||||
- Remove deprecated methods `Python::is_instance`, `Python::is_subclass`, `Python::release`, `Python::xdecref`, and `Py::from_owned_ptr_or_panic`. [#1426](https://github.com/PyO3/pyo3/pull/1426)
|
||||
- Remove many FFI definitions which never existed in the Python C-API:
|
||||
- (previously deprecated) `PyGetSetDef_INIT`, `PyGetSetDef_DICT`, `PyCoro_Check`, `PyCoroWrapper_Check`, and `PyAsyncGen_Check` [#1426](https://github.com/PyO3/pyo3/pull/1426)
|
||||
- `PyMethodDef_INIT` [#1426](https://github.com/PyO3/pyo3/pull/1426)
|
||||
- `PyTypeObject_INIT` [#1429](https://github.com/PyO3/pyo3/pull/1429)
|
||||
- `PyObject_Check`, `PySuper_Check`, and `FreeFunc` [#1438](https://github.com/PyO3/pyo3/pull/1438)
|
||||
- `PyModuleDef_INIT` [#1630](https://github.com/PyO3/pyo3/pull/1630)
|
||||
- Remove pyclass implementation details from `PyTypeInfo`:
|
||||
- `Type`, `DESCRIPTION`, and `FLAGS` [#1456](https://github.com/PyO3/pyo3/pull/1456)
|
||||
- `BaseType`, `BaseLayout`, `Layout`, `Initializer` [#1596](https://github.com/PyO3/pyo3/pull/1596)
|
||||
- `PyModuleDef_INIT` [#1630](https://github.com/PyO3/pyo3/pull/1630)
|
||||
- Remove `__doc__` from module's `__all__`. [#1509](https://github.com/PyO3/pyo3/pull/1509)
|
||||
- Remove `PYO3_CROSS_INCLUDE_DIR` environment variable and the associated C header parsing functionality. [#1521](https://github.com/PyO3/pyo3/pull/1521)
|
||||
- Remove `raw_pycfunction!` macro. [#1619](https://github.com/PyO3/pyo3/pull/1619)
|
||||
- Remove `PyClassAlloc` trait. [#1657](https://github.com/PyO3/pyo3/pull/1657)
|
||||
- Remove `PyList::get_parked_item`. [#1664](https://github.com/PyO3/pyo3/pull/1664)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Remove FFI definition `PyCFunction_ClearFreeList` for Python 3.9 and later. [#1425](https://github.com/PyO3/pyo3/pull/1425)
|
||||
- `PYO3_CROSS_LIB_DIR` enviroment variable no long required when compiling for x86-64 Python from macOS arm64 and reverse. [#1428](https://github.com/PyO3/pyo3/pull/1428)
|
||||
- Fix FFI definition `_PyEval_RequestCodeExtraIndex` which took an argument of the wrong type. [#1429](https://github.com/PyO3/pyo3/pull/1429)
|
||||
- Fix FFI definition `_PyEval_RequestCodeExtraIndex`, which took an argument of the wrong type. [#1429](https://github.com/PyO3/pyo3/pull/1429)
|
||||
- Fix FFI definition `PyIndex_Check` missing with the `abi3` feature. [#1436](https://github.com/PyO3/pyo3/pull/1436)
|
||||
- Fix incorrect `TypeError` raised when keyword-only argument passed along with a positional argument in `*args`. [#1440](https://github.com/PyO3/pyo3/pull/1440)
|
||||
- Fix inability to use a named lifetime for `&PyTuple` of `*args` in `#[pyfunction]`. [#1440](https://github.com/PyO3/pyo3/pull/1440)
|
||||
- Fix inability to add `#[text_signature]` to some `#[pyproto]` methods. [#1483](https://github.com/PyO3/pyo3/pull/1483)
|
||||
- Fix use of Python argument for #[pymethods] inside macro expansions. [#1505](https://github.com/PyO3/pyo3/pull/1505)
|
||||
- Always use cross-compiling configuration if any of the environment variables are set. [#1514](https://github.com/PyO3/pyo3/pull/1514)
|
||||
- Fix use of Python argument for `#[pymethods]` inside macro expansions. [#1505](https://github.com/PyO3/pyo3/pull/1505)
|
||||
- No longer include `__doc__` in `__all__` generated for `#[pymodule]`. [#1509](https://github.com/PyO3/pyo3/pull/1509)
|
||||
- Always use cross-compiling configuration if any of the `PYO3_CROSS` family of environment variables are set. [#1514](https://github.com/PyO3/pyo3/pull/1514)
|
||||
- Support `EnvironmentError`, `IOError`, and `WindowsError` on PyPy. [#1533](https://github.com/PyO3/pyo3/pull/1533)
|
||||
- Fix unneccessary rebuilds when cycling between `cargo check` and `cargo clippy` in a Python virtualenv. [#1557](https://github.com/PyO3/pyo3/pull/1557)
|
||||
- Fix segfault when dereferencing `ffi::PyDateTimeAPI` without the GIL. [#1563](https://github.com/PyO3/pyo3/pull/1563)
|
||||
- Fix memory leak when converting to u128 and i128. [#1638](https://github.com/PyO3/pyo3/pull/1638)
|
||||
- Fix memory leak in `FromPyObject` implementations for `u128` and `i128`. [#1638](https://github.com/PyO3/pyo3/pull/1638)
|
||||
- Fix `#[pyclass(extends=PyDict)]` leaking the dict contents on drop. [#1657](https://github.com/PyO3/pyo3/pull/1657)
|
||||
- Fix segfault when calling `PyList::get_item` with negative indices. [#1668](https://github.com/PyO3/pyo3/pull/1668)
|
||||
- Fix FFI definitions of `PyEval_SetProfile`/`PyEval_SetTrace` to take `Option<Py_tracefunc>` parameters. [#1692](https://github.com/PyO3/pyo3/pull/1692)
|
||||
- Fix `ToPyObject` impl for `HashSet` to accept non-default hashers. [#1702](https://github.com/PyO3/pyo3/pull/1702)
|
||||
|
||||
## [0.13.2] - 2021-02-12
|
||||
|
||||
### Packaging
|
||||
|
||||
- Lower minimum supported Rust version to 1.41. [#1421](https://github.com/PyO3/pyo3/pull/1421)
|
||||
|
||||
### Added
|
||||
- Add unsafe API `with_embedded_python_interpreter` to initalize a Python interpreter, execute a closure, and finalize the interpreter. [#1355](https://github.com/PyO3/pyo3/pull/1355)
|
||||
|
||||
- Add unsafe API `with_embedded_python_interpreter` to initalize a Python interpreter, execute a closure, and finalize the interpreter. [#1355](https://github.com/PyO3/pyo3/pull/1355)
|
||||
- Add `serde` feature which provides implementations of `Serialize` and `Deserialize` for `Py<T>`. [#1366](https://github.com/PyO3/pyo3/pull/1366)
|
||||
- Add FFI definition `_PyCFunctionFastWithKeywords` on Python 3.7 and up. [#1384](https://github.com/PyO3/pyo3/pull/1384)
|
||||
- Add `PyDateTime::new_with_fold()` method. [#1398](https://github.com/PyO3/pyo3/pull/1398)
|
||||
- Add `size_hint` impls for `{PyDict,PyList,PySet,PyTuple}Iterator`s. [#1699](https://github.com/PyO3/pyo3/pull/1699)
|
||||
|
||||
### Changed
|
||||
|
||||
- `prepare_freethreaded_python` will no longer register an `atexit` handler to call `Py_Finalize`. This resolves a number of issues with incompatible C extensions causing crashes at finalization. [#1355](https://github.com/PyO3/pyo3/pull/1355)
|
||||
- Mark `PyLayout::py_init`, `PyClassDict::clear_dict`, and `opt_to_pyobj` safe, as they do not perform any unsafe operations. [#1404](https://github.com/PyO3/pyo3/pull/1404)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix support for using `r#raw_idents` as argument names in pyfunctions. [#1383](https://github.com/PyO3/pyo3/pull/1383)
|
||||
- Fix typo in FFI definition for `PyFunction_GetCode` (was incorrectly `PyFunction_Code`). [#1387](https://github.com/PyO3/pyo3/pull/1387)
|
||||
- Fix FFI definitions `PyMarshal_WriteObjectToString` and `PyMarshal_ReadObjectFromString` as available in limited API. [#1387](https://github.com/PyO3/pyo3/pull/1387)
|
||||
|
@ -101,13 +159,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Fix build on mingw / MSYS2. [#1423](https://github.com/PyO3/pyo3/pull/1423)
|
||||
|
||||
## [0.13.1] - 2021-01-10
|
||||
|
||||
### Added
|
||||
|
||||
- Add support for `#[pyclass(dict)]` and `#[pyclass(weakref)]` with the `abi3` feature on Python 3.9 and up. [#1342](https://github.com/PyO3/pyo3/pull/1342)
|
||||
- Add FFI definitions `PyOS_BeforeFork`, `PyOS_AfterFork_Parent`, `PyOS_AfterFork_Child` for Python 3.7 and up. [#1348](https://github.com/PyO3/pyo3/pull/1348)
|
||||
- Add an `auto-initialize` feature to control whether PyO3 should automatically initialize an embedded Python interpreter. For compatibility this feature is enabled by default in PyO3 0.13.1, but is planned to become opt-in from PyO3 0.14.0. [#1347](https://github.com/PyO3/pyo3/pull/1347)
|
||||
- Add support for cross-compiling to Windows without needing `PYO3_CROSS_INCLUDE_DIR`. [#1350](https://github.com/PyO3/pyo3/pull/1350)
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecate FFI definitions `PyEval_CallObjectWithKeywords`, `PyEval_CallObject`, `PyEval_CallFunction`, `PyEval_CallMethod` when building for Python 3.9. [#1338](https://github.com/PyO3/pyo3/pull/1338)
|
||||
- Deprecate FFI definitions `PyGetSetDef_DICT` and `PyGetSetDef_INIT` which have never been in the Python API. [#1341](https://github.com/PyO3/pyo3/pull/1341)
|
||||
- Deprecate FFI definitions `PyGen_NeedsFinalizing`, `PyImport_Cleanup` (removed in 3.9), and `PyOS_InitInterrupts` (3.10). [#1348](https://github.com/PyO3/pyo3/pull/1348)
|
||||
|
@ -116,11 +177,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Deprecate FFI definitions for `PyUnicode_FromUnicode`, `PyUnicode_AsUnicode` and `PyUnicode_AsUnicodeAndSize`, which will be removed from 3.12 and up due to [PEP 613](https://www.python.org/dev/peps/pep-0623/). [#1370](https://github.com/PyO3/pyo3/pull/1370)
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove FFI definition `PyFrame_ClearFreeList` when building for Python 3.9. [#1341](https://github.com/PyO3/pyo3/pull/1341)
|
||||
- Remove FFI definition `_PyDict_Contains` when building for Python 3.10. [#1341](https://github.com/PyO3/pyo3/pull/1341)
|
||||
- Remove FFI definitions `PyGen_NeedsFinalizing` and `PyImport_Cleanup` (for 3.9 and up), and `PyOS_InitInterrupts` (3.10). [#1348](https://github.com/PyO3/pyo3/pull/1348)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Stop including `Py_TRACE_REFS` config setting automatically if `Py_DEBUG` is set on Python 3.8 and up. [#1334](https://github.com/PyO3/pyo3/pull/1334)
|
||||
- Remove `#[deny(warnings)]` attribute (and instead refuse warnings only in CI). [#1340](https://github.com/PyO3/pyo3/pull/1340)
|
||||
- Fix deprecation warning for missing `__module__` with `#[pyclass]`. [#1343](https://github.com/PyO3/pyo3/pull/1343)
|
||||
|
@ -128,7 +191,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Fix missing `Py_INCREF` on heap type objects on Python versions before 3.8. [#1365](https://github.com/PyO3/pyo3/pull/1365)
|
||||
|
||||
## [0.13.0] - 2020-12-22
|
||||
|
||||
### Packaging
|
||||
|
||||
- Drop support for Python 3.5 (as it is now end-of-life). [#1250](https://github.com/PyO3/pyo3/pull/1250)
|
||||
- Bump minimum supported Rust version to 1.45. [#1272](https://github.com/PyO3/pyo3/pull/1272)
|
||||
- Bump indoc dependency to 1.0. [#1272](https://github.com/PyO3/pyo3/pull/1272)
|
||||
|
@ -136,6 +201,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Rename internal crates `pyo3cls` and `pyo3-derive-backend` to `pyo3-macros` and `pyo3-macros-backend` respectively. [#1317](https://github.com/PyO3/pyo3/pull/1317)
|
||||
|
||||
### Added
|
||||
|
||||
- Add support for building for CPython limited API. Opting-in to the limited API enables a single extension wheel built with PyO3 to be installable on multiple Python versions. This required a few minor changes to runtime behaviour of of PyO3 `#[pyclass]` types. See the migration guide for full details. [#1152](https://github.com/PyO3/pyo3/pull/1152)
|
||||
- Add feature flags `abi3-py36`, `abi3-py37`, `abi3-py38` etc. to set the minimum Python version when using the limited API. [#1263](https://github.com/PyO3/pyo3/pull/1263)
|
||||
- Add argument names to `TypeError` messages generated by pymethod wrappers. [#1212](https://github.com/PyO3/pyo3/pull/1212)
|
||||
|
@ -150,6 +216,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Add conversions for tuples of length 10, 11, and 12. [#1454](https://github.com/PyO3/pyo3/pull/1454)
|
||||
|
||||
### Changed
|
||||
|
||||
- Change return type of `PyType::name()` from `Cow<str>` to `PyResult<&str>`. [#1152](https://github.com/PyO3/pyo3/pull/1152)
|
||||
- `#[pyclass(subclass)]` is now required for subclassing from Rust (was previously just required for subclassing from Python). [#1152](https://github.com/PyO3/pyo3/pull/1152)
|
||||
- Change `PyIterator` to be consistent with other native types: it is now used as `&PyIterator` instead of `PyIterator<'a>`. [#1176](https://github.com/PyO3/pyo3/pull/1176)
|
||||
|
@ -161,13 +228,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Require double-quotes for pyclass name argument e.g `#[pyclass(name = "MyClass")]`. [#1303](https://github.com/PyO3/pyo3/pull/1303)
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecate `Python::is_instance`, `Python::is_subclass`, `Python::release`, and `Python::xdecref`. [#1292](https://github.com/PyO3/pyo3/pull/1292)
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove deprecated ffi definitions `PyUnicode_AsUnicodeCopy`, `PyUnicode_GetMax`, `_Py_CheckRecursionLimit`, `PyObject_AsCharBuffer`, `PyObject_AsReadBuffer`, `PyObject_CheckReadBuffer` and `PyObject_AsWriteBuffer`, which will be removed in Python 3.10. [#1217](https://github.com/PyO3/pyo3/pull/1217)
|
||||
- Remove unused `python3` feature. [#1235](https://github.com/PyO3/pyo3/pull/1235)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix missing field in `PyCodeObject` struct (`co_posonlyargcount`) - caused invalid access to other fields in Python >3.7. [#1260](https://github.com/PyO3/pyo3/pull/1260)
|
||||
- Fix building for `x86_64-unknown-linux-musl` target from `x86_64-unknown-linux-gnu` host. [#1267](https://github.com/PyO3/pyo3/pull/1267)
|
||||
- Fix `#[text_signature]` interacting badly with rust `r#raw_identifiers`. [#1286](https://github.com/PyO3/pyo3/pull/1286)
|
||||
|
@ -177,30 +247,41 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Fix using custom error type in pyclass `#[new]` methods. [#1319](https://github.com/PyO3/pyo3/pull/1319)
|
||||
|
||||
## [0.12.4] - 2020-11-28
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix reference count bug in implementation of `From<Py<T>>` for `PyObject`, a regression introduced in PyO3 0.12. [#1297](https://github.com/PyO3/pyo3/pull/1297)
|
||||
|
||||
## [0.12.3] - 2020-10-12
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix support for Rust versions 1.39 to 1.44, broken by an incorrect internal update to paste 1.0 which was done in PyO3 0.12.2. [#1234](https://github.com/PyO3/pyo3/pull/1234)
|
||||
|
||||
## [0.12.2] - 2020-10-12
|
||||
|
||||
### Added
|
||||
|
||||
- Add support for keyword-only arguments without default values in `#[pyfunction]`. [#1209](https://github.com/PyO3/pyo3/pull/1209)
|
||||
- Add `Python::check_signals()` as a safe a wrapper for `PyErr_CheckSignals()`. [#1214](https://github.com/PyO3/pyo3/pull/1214)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix invalid document for protocol methods. [#1169](https://github.com/PyO3/pyo3/pull/1169)
|
||||
- Hide docs of PyO3 private implementation details in `pyo3::class::methods`. [#1169](https://github.com/PyO3/pyo3/pull/1169)
|
||||
- Fix unnecessary rebuild on PATH changes when the python interpreter is provided by PYO3_PYTHON. [#1231](https://github.com/PyO3/pyo3/pull/1231)
|
||||
|
||||
## [0.12.1] - 2020-09-16
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix building for a 32-bit Python on 64-bit Windows with a 64-bit Rust toolchain. [#1179](https://github.com/PyO3/pyo3/pull/1179)
|
||||
- Fix building on platforms where `c_char` is `u8`. [#1182](https://github.com/PyO3/pyo3/pull/1182)
|
||||
|
||||
## [0.12.0] - 2020-09-12
|
||||
|
||||
### Added
|
||||
|
||||
- Add FFI definitions `Py_FinalizeEx`, `PyOS_getsig`, and `PyOS_setsig`. [#1021](https://github.com/PyO3/pyo3/pull/1021)
|
||||
- Add `PyString::to_str` for accessing `PyString` as `&str`. [#1023](https://github.com/PyO3/pyo3/pull/1023)
|
||||
- Add `Python::with_gil` for executing a closure with the Python GIL. [#1037](https://github.com/PyO3/pyo3/pull/1037)
|
||||
|
@ -216,6 +297,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Add native `PyCFunction` and `PyFunction` types. [#1163](https://github.com/PyO3/pyo3/pull/1163)
|
||||
|
||||
### Changed
|
||||
|
||||
- Rework exception types: [#1024](https://github.com/PyO3/pyo3/pull/1024) [#1115](https://github.com/PyO3/pyo3/pull/1115)
|
||||
- Rename exception types from e.g. `RuntimeError` to `PyRuntimeError`. The old names continue to exist but are deprecated.
|
||||
- Exception objects are now accessible as `&T` or `Py<T>`, just like other Python-native types.
|
||||
|
@ -240,6 +322,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Change argument to `PyModule::add` to `impl IntoPy<PyObject>` (was `impl ToPyObject`). #[1124](https://github.com/PyO3/pyo3/pull/1124)
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove many exception and `PyErr` APIs; see the "changed" section above. [#1024](https://github.com/PyO3/pyo3/pull/1024) [#1067](https://github.com/PyO3/pyo3/pull/1067) [#1115](https://github.com/PyO3/pyo3/pull/1115)
|
||||
- Remove `PyString::to_string` (use new `PyString::to_str`). [#1023](https://github.com/PyO3/pyo3/pull/1023)
|
||||
- Remove `PyString::as_bytes`. [#1023](https://github.com/PyO3/pyo3/pull/1023)
|
||||
|
@ -249,6 +332,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Remove the `AsPyRef` trait. [#1098](https://github.com/PyO3/pyo3/pull/1098)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Correct FFI definitions `Py_SetProgramName` and `Py_SetPythonHome` to take `*const` arguments (was `*mut`). [#1021](https://github.com/PyO3/pyo3/pull/1021)
|
||||
- Fix `FromPyObject` for `num_bigint::BigInt` for Python objects with an `__index__` method. [#1027](https://github.com/PyO3/pyo3/pull/1027)
|
||||
- Correct FFI definition `_PyLong_AsByteArray` to take `*mut c_uchar` argument (was `*const c_uchar`). [#1029](https://github.com/PyO3/pyo3/pull/1029)
|
||||
|
@ -258,18 +342,23 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Fix many cases of lifetime elision in `#[pyproto]` implementations. [#1093](https://github.com/PyO3/pyo3/pull/1093)
|
||||
- Fix detection of Python build configuration when cross-compiling. [#1095](https://github.com/PyO3/pyo3/pull/1095)
|
||||
- Always link against libpython on android with the `extension-module` feature. [#1095](https://github.com/PyO3/pyo3/pull/1095)
|
||||
- Fix the `+` operator not trying `__radd__` when both `__add__` and `__radd__` are defined in `PyNumberProtocol` (and similar for all other reversible operators). [#1107](https://github.com/PyO3/pyo3/pull/1107)
|
||||
- Fix the `+` operator not trying `__radd__` when both `__add__` and `__radd__` are defined in `PyNumberProtocol` (and similar for all other reversible operators). [#1107](https://github.com/PyO3/pyo3/pull/1107)
|
||||
- Fix building with Anaconda python. [#1175](https://github.com/PyO3/pyo3/pull/1175)
|
||||
|
||||
## [0.11.1] - 2020-06-30
|
||||
|
||||
### Added
|
||||
|
||||
- `#[pyclass(unsendable)]`. [#1009](https://github.com/PyO3/pyo3/pull/1009)
|
||||
|
||||
### Changed
|
||||
|
||||
- Update `parking_lot` dependency to `0.11`. [#1010](https://github.com/PyO3/pyo3/pull/1010)
|
||||
|
||||
## [0.11.0] - 2020-06-28
|
||||
|
||||
### Added
|
||||
|
||||
- Support stable versions of Rust (>=1.39). [#969](https://github.com/PyO3/pyo3/pull/969)
|
||||
- Add FFI definition `PyObject_AsFileDescriptor`. [#938](https://github.com/PyO3/pyo3/pull/938)
|
||||
- Add `PyByteArray::data`, `PyByteArray::as_bytes`, and `PyByteArray::as_bytes_mut`. [#967](https://github.com/PyO3/pyo3/pull/967)
|
||||
|
@ -278,6 +367,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Add `IterNextOutput` and `IterANextOutput` for returning from `__next__` / `__anext__`. [#997](https://github.com/PyO3/pyo3/pull/997)
|
||||
|
||||
### Changed
|
||||
|
||||
- Simplify internals of `#[pyo3(get)]` attribute. (Remove the hidden API `GetPropertyValue`.) [#934](https://github.com/PyO3/pyo3/pull/934)
|
||||
- Call `Py_Finalize` at exit to flush buffers, etc. [#943](https://github.com/PyO3/pyo3/pull/943)
|
||||
- Add type parameter to PyBuffer. #[951](https://github.com/PyO3/pyo3/pull/951)
|
||||
|
@ -295,20 +385,26 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- `#[pyproto]` methods can now skip annotating the return type if it is `()`. [#998](https://github.com/PyO3/pyo3/pull/998)
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove `ManagedPyRef` (unused, and needs specialization) [#930](https://github.com/PyO3/pyo3/pull/930)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix passing explicit `None` to `Option<T>` argument `#[pyfunction]` with a default value. [#936](https://github.com/PyO3/pyo3/pull/936)
|
||||
- Fix `PyClass.__new__`'s not respecting subclasses when inherited by a Python class. [#990](https://github.com/PyO3/pyo3/pull/990)
|
||||
- Fix returning `Option<T>` from `#[pyproto]` methods. [#996](https://github.com/PyO3/pyo3/pull/996)
|
||||
- Fix accepting `PyRef<Self>` and `PyRefMut<Self>` to `#[getter]` and `#[setter]` methods. [#999](https://github.com/PyO3/pyo3/pull/999)
|
||||
|
||||
## [0.10.1] - 2020-05-14
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix deadlock in `Python::acquire_gil()` after dropping a `PyObject` or `Py<T>`. [#924](https://github.com/PyO3/pyo3/pull/924)
|
||||
|
||||
## [0.10.0] - 2020-05-13
|
||||
|
||||
### Added
|
||||
|
||||
- Add FFI definition `_PyDict_NewPresized`. [#849](https://github.com/PyO3/pyo3/pull/849)
|
||||
- Implement `IntoPy<PyObject>` for `HashSet` and `BTreeSet`. [#864](https://github.com/PyO3/pyo3/pull/864)
|
||||
- Add `PyAny::dir` method. [#886](https://github.com/PyO3/pyo3/pull/886)
|
||||
|
@ -320,6 +416,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Add `#[classattr]` support for associated constants in `#[pymethods]`. [#914](https://github.com/PyO3/pyo3/pull/914)
|
||||
|
||||
### Changed
|
||||
|
||||
- Panics will now be raised as a Python `PanicException`. [#797](https://github.com/PyO3/pyo3/pull/797)
|
||||
- Change `PyObject` and `Py<T>` reference counts to decrement immediately upon drop when the GIL is held. [#851](https://github.com/PyO3/pyo3/pull/851)
|
||||
- Allow `PyIterProtocol` methods to use either `PyRef` or `PyRefMut` as the receiver type. [#856](https://github.com/PyO3/pyo3/pull/856)
|
||||
|
@ -328,6 +425,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Remove need for `#![feature(specialization)]` in crates depending on PyO3. [#917](https://github.com/PyO3/pyo3/pull/917)
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove `PyMethodsProtocol` trait. [#889](https://github.com/PyO3/pyo3/pull/889)
|
||||
- Remove `num-traits` dependency. [#895](https://github.com/PyO3/pyo3/pull/895)
|
||||
- Remove `ObjectProtocol` trait. [#911](https://github.com/PyO3/pyo3/pull/911)
|
||||
|
@ -335,6 +433,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Remove all `*ProtocolImpl` traits. [#917](https://github.com/PyO3/pyo3/pull/917)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix support for `__radd__` and other `__r*__` methods as implementations for Python mathematical operators. [#839](https://github.com/PyO3/pyo3/pull/839)
|
||||
- Fix panics during garbage collection when traversing objects that were already mutably borrowed. [#855](https://github.com/PyO3/pyo3/pull/855)
|
||||
- Prevent `&'static` references to Python objects as arguments to `#[pyfunction]` and `#[pymethods]`. [#869](https://github.com/PyO3/pyo3/pull/869)
|
||||
|
@ -345,19 +444,26 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Fix segmentatation faults when a panic occurs during a call to `Python::allow_threads`. [#912](https://github.com/PyO3/pyo3/pull/912)
|
||||
|
||||
## [0.9.2] - 2020-04-09
|
||||
|
||||
### Added
|
||||
|
||||
- `FromPyObject` implementations for `HashSet` and `BTreeSet`. [#842](https://github.com/PyO3/pyo3/pull/842)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Correctly detect 32bit architecture. [#830](https://github.com/PyO3/pyo3/pull/830)
|
||||
|
||||
## [0.9.1] - 2020-03-23
|
||||
|
||||
### Fixed
|
||||
|
||||
- Error messages for `#[pyclass]`. [#826](https://github.com/PyO3/pyo3/pull/826)
|
||||
- `FromPyObject` implementation for `PySequence`. [#827](https://github.com/PyO3/pyo3/pull/827)
|
||||
|
||||
## [0.9.0] - 2020-03-19
|
||||
|
||||
### Added
|
||||
|
||||
- `PyCell`, which has RefCell-like features. [#770](https://github.com/PyO3/pyo3/pull/770)
|
||||
- `PyClass`, `PyLayout`, `PyClassInitializer`. [#683](https://github.com/PyO3/pyo3/pull/683)
|
||||
- Implemented `IntoIterator` for `PySet` and `PyFrozenSet`. [#716](https://github.com/PyO3/pyo3/pull/716)
|
||||
|
@ -371,6 +477,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- `ffi::{_PyBytes_Resize, _PyDict_Next, _PyDict_Contains, _PyDict_GetDictPtr}`. #[820](https://github.com/PyO3/pyo3/pull/820)
|
||||
|
||||
### Changed
|
||||
|
||||
- `#[new]` does not take `PyRawObject` and can return `Self`. [#683](https://github.com/PyO3/pyo3/pull/683)
|
||||
- The blanket implementations for `FromPyObject` for `&T` and `&mut T` are no longer specializable. Implement `PyTryFrom` for your type to control the behavior of `FromPyObject::extract()` for your types. [#713](https://github.com/PyO3/pyo3/pull/713)
|
||||
- The implementation for `IntoPy<U> for T` where `U: FromPy<T>` is no longer specializable. Control the behavior of this via the implementation of `FromPy`. [#713](https://github.com/PyO3/pyo3/pull/713)
|
||||
|
@ -381,6 +488,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- `PyAny` is now on the top level module and prelude. [#816](https://github.com/PyO3/pyo3/pull/816)
|
||||
|
||||
### Removed
|
||||
|
||||
- `PyRawObject`. [#683](https://github.com/PyO3/pyo3/pull/683)
|
||||
- `PyNoArgsFunction`. [#741](https://github.com/PyO3/pyo3/pull/741)
|
||||
- `initialize_type()`. To set the module name for a `#[pyclass]`, use the `module` argument to the macro. #[751](https://github.com/PyO3/pyo3/pull/751)
|
||||
|
@ -390,6 +498,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- `ObjectProtocol::get_base/get_mut_base`. [#770](https://github.com/PyO3/pyo3/pull/770)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed unsoundness of subclassing. [#683](https://github.com/PyO3/pyo3/pull/683).
|
||||
- Clear error indicator when the exception is handled on the Rust side. [#719](https://github.com/PyO3/pyo3/pull/719)
|
||||
- Usage of raw identifiers with `#[pyo3(set)]`. [#745](https://github.com/PyO3/pyo3/pull/745)
|
||||
|
@ -400,67 +509,87 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Fix the case where `DESCRIPTION` is not null-terminated. #[822](https://github.com/PyO3/pyo3/pull/822)
|
||||
|
||||
## [0.8.5] - 2020-01-05
|
||||
|
||||
### Added
|
||||
|
||||
- Implemented `FromPyObject` for `HashMap` and `BTreeMap`
|
||||
- Support for `#[name = "foo"]` attribute for `#[pyfunction]` and in `#[pymethods]`. [#692](https://github.com/PyO3/pyo3/pull/692)
|
||||
|
||||
## [0.8.4] - 2019-12-14
|
||||
|
||||
### Added
|
||||
|
||||
- Support for `#[text_signature]` attribute. [#675](https://github.com/PyO3/pyo3/pull/675)
|
||||
|
||||
## [0.8.3] - 2019-11-23
|
||||
|
||||
### Removed
|
||||
|
||||
- `#[init]` is removed. [#658](https://github.com/PyO3/pyo3/pull/658)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Now all `&Py~` types have `!Send` bound. [#655](https://github.com/PyO3/pyo3/pull/655)
|
||||
- Fix a compile error raised by the stabilization of `!` type. [#672](https://github.com/PyO3/pyo3/issues/672).
|
||||
|
||||
## [0.8.2] - 2019-10-27
|
||||
|
||||
### Added
|
||||
|
||||
- FFI compatibility for PEP 590 Vectorcall. [#641](https://github.com/PyO3/pyo3/pull/641)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix PySequenceProtocol::set_item. [#624](https://github.com/PyO3/pyo3/pull/624)
|
||||
- Fix a corner case of BigInt::FromPyObject. [#630](https://github.com/PyO3/pyo3/pull/630)
|
||||
- Fix index errors in parameter conversion. [#631](https://github.com/PyO3/pyo3/pull/631)
|
||||
- Fix handling of invalid utf-8 sequences in `PyString::as_bytes`. [#639](https://github.com/PyO3/pyo3/pull/639)
|
||||
and `PyString::to_string_lossy` [#642](https://github.com/PyO3/pyo3/pull/642).
|
||||
and `PyString::to_string_lossy` [#642](https://github.com/PyO3/pyo3/pull/642).
|
||||
- Remove `__contains__` and `__iter__` from PyMappingProtocol. [#644](https://github.com/PyO3/pyo3/pull/644)
|
||||
- Fix proc-macro definition of PySetAttrProtocol. [#645](https://github.com/PyO3/pyo3/pull/645)
|
||||
|
||||
## [0.8.1] - 2019-10-08
|
||||
|
||||
### Added
|
||||
|
||||
- Conversion between [num-bigint](https://github.com/rust-num/num-bigint) and Python int. [#608](https://github.com/PyO3/pyo3/pull/608)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Make sure the right Python interpreter is used in OSX builds. [#604](https://github.com/PyO3/pyo3/pull/604)
|
||||
- Patch specialization being broken by Rust 1.40. [#614](https://github.com/PyO3/pyo3/issues/614)
|
||||
- Fix a segfault around PyErr. [#597](https://github.com/PyO3/pyo3/pull/597)
|
||||
|
||||
## [0.8.0] - 2019-09-16
|
||||
|
||||
### Added
|
||||
|
||||
- `module` argument to `pyclass` macro. [#499](https://github.com/PyO3/pyo3/pull/499)
|
||||
- `py_run!` macro [#512](https://github.com/PyO3/pyo3/pull/512)
|
||||
- Use existing fields and methods before calling custom __getattr__. [#505](https://github.com/PyO3/pyo3/pull/505)
|
||||
- Use existing fields and methods before calling custom **getattr**. [#505](https://github.com/PyO3/pyo3/pull/505)
|
||||
- `PyBytes` can now be indexed just like `Vec<u8>`
|
||||
- Implement `IntoPy<PyObject>` for `PyRef` and `PyRefMut`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Implementing the Using the `gc` parameter for `pyclass` (e.g. `#[pyclass(gc)]`) without implementing the `class::PyGCProtocol` trait is now a compile-time error. Failing to implement this trait could lead to segfaults. [#532](https://github.com/PyO3/pyo3/pull/532)
|
||||
- `PyByteArray::data` has been replaced with `PyDataArray::to_vec` because returning a `&[u8]` is unsound. (See [this comment](https://github.com/PyO3/pyo3/issues/373#issuecomment-512332696) for a great write-up for why that was unsound)
|
||||
- Replace `mashup` with `paste`.
|
||||
- `GILPool` gained a `Python` marker to prevent it from being misused to release Python objects without the GIL held.
|
||||
|
||||
### Removed
|
||||
|
||||
- `IntoPyObject` was replaced with `IntoPy<PyObject>`
|
||||
- `#[pyclass(subclass)]` is hidden a `unsound-subclass` feature because it's causing segmentation faults.
|
||||
|
||||
### Fixed
|
||||
|
||||
- More readable error message for generics in pyclass [#503](https://github.com/PyO3/pyo3/pull/503)
|
||||
|
||||
## [0.7.0] - 2019-05-26
|
||||
|
||||
### Added
|
||||
|
||||
- PyPy support by omerbenamram in [#393](https://github.com/PyO3/pyo3/pull/393)
|
||||
- Have `PyModule` generate an index of its members (`__all__` list).
|
||||
- Allow `slf: PyRef<T>` for pyclass(#419)
|
||||
|
@ -468,25 +597,29 @@ and `PyString::to_string_lossy` [#642](https://github.com/PyO3/pyo3/pull/642).
|
|||
- Add `marshal` module. [#460](https://github.com/PyO3/pyo3/pull/460)
|
||||
|
||||
### Changed
|
||||
|
||||
- `Python::run` returns `PyResult<()>` instead of `PyResult<&PyAny>`.
|
||||
- Methods decorated with `#[getter]` and `#[setter]` can now omit wrapping the
|
||||
result type in `PyResult` if they don't raise exceptions.
|
||||
result type in `PyResult` if they don't raise exceptions.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `type_object::PyTypeObject` has been marked unsafe because breaking the contract `type_object::PyTypeObject::init_type` can lead to UB.
|
||||
- Fixed automatic derive of `PySequenceProtocol` implementation in [#423](https://github.com/PyO3/pyo3/pull/423).
|
||||
- Capitalization & better wording to README.md.
|
||||
- Docstrings of properties is now properly set using the doc of the `#[getter]` method.
|
||||
- Fixed issues with `pymethods` crashing on doc comments containing double quotes.
|
||||
- `PySet::new` and `PyFrozenSet::new` now return `PyResult<&Py[Frozen]Set>`; exceptions are raised if
|
||||
the items are not hashable.
|
||||
the items are not hashable.
|
||||
- Fixed building using `venv` on Windows.
|
||||
- `PyTuple::new` now returns `&PyTuple` instead of `Py<PyTuple>`.
|
||||
- Fixed several issues with argument parsing; notable, the `*args` and `**kwargs`
|
||||
tuple/dict now doesn't contain arguments that are otherwise assigned to parameters.
|
||||
tuple/dict now doesn't contain arguments that are otherwise assigned to parameters.
|
||||
|
||||
## [0.6.0] - 2019-03-28
|
||||
|
||||
### Regressions
|
||||
|
||||
- Currently, [#341](https://github.com/PyO3/pyo3/issues/341) causes `cargo test` to fail with weird linking errors when the `extension-module` feature is activated. For now you can work around this by making the `extension-module` feature optional and running the tests with `cargo test --no-default-features`:
|
||||
|
||||
```toml
|
||||
|
@ -499,6 +632,7 @@ default = ["extension-module"]
|
|||
```
|
||||
|
||||
### Added
|
||||
|
||||
- Added a `wrap_pymodule!` macro similar to the existing `wrap_pyfunction!` macro. Only available on python 3
|
||||
- Added support for cross compiling (e.g. to arm v7) by mtp401 in [#327](https://github.com/PyO3/pyo3/pull/327). See the "Cross Compiling" section in the "Building and Distribution" chapter of the guide for more details.
|
||||
- The `PyRef` and `PyRefMut` types, which allow to differentiate between an instance of a rust struct on the rust heap and an instance that is embedded inside a python object. By kngwyu in [#335](https://github.com/PyO3/pyo3/pull/335)
|
||||
|
@ -506,6 +640,7 @@ default = ["extension-module"]
|
|||
- Added `ManagedPyRef`, which should eventually replace `ToBorrowedObject`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Renamed `PyObjectRef` to `PyAny` in #388
|
||||
- Renamed `add_function` to `add_wrapped` as it now also supports modules.
|
||||
- Renamed `#[pymodinit]` to `#[pymodule]`
|
||||
|
@ -528,29 +663,38 @@ default = ["extension-module"]
|
|||
- Renamed the `typeob` module to `type_object`
|
||||
|
||||
### Removed
|
||||
|
||||
- `PyToken` was removed due to unsoundness (See [#94](https://github.com/PyO3/pyo3/issues/94)).
|
||||
- Removed the unnecessary type parameter from `PyObjectAlloc`
|
||||
- `NoArgs`. Just use an empty tuple
|
||||
- `PyObjectWithGIL`. `PyNativeType` is sufficient now that PyToken is removed.
|
||||
|
||||
### Fixed
|
||||
|
||||
- A soudness hole where every instances of a `#[pyclass]` struct was considered to be part of a python object, even though you can create instances that are not part of the python heap. This was fixed through `PyRef` and `PyRefMut`.
|
||||
- Fix kwargs support in [#328](https://github.com/PyO3/pyo3/pull/328).
|
||||
- Add full support for `__dict__` in [#403](https://github.com/PyO3/pyo3/pull/403).
|
||||
|
||||
## [0.5.3] - 2019-01-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix memory leak in ArrayList by kngwyu [#316](https://github.com/PyO3/pyo3/pull/316)
|
||||
|
||||
## [0.5.2] - 2018-11-25
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix undeterministic segfaults when creating many objects by kngwyu in [#281](https://github.com/PyO3/pyo3/pull/281)
|
||||
|
||||
## [0.5.1] - 2018-11-24
|
||||
|
||||
Yanked
|
||||
|
||||
## [0.5.0] - 2018-11-11
|
||||
|
||||
### Added
|
||||
|
||||
- `#[pyclass]` objects can now be returned from rust functions
|
||||
- `PyComplex` by kngwyu in [#226](https://github.com/PyO3/pyo3/pull/226)
|
||||
- `PyDict::from_sequence()`, equivalent to `dict([(key, val), ...])`
|
||||
|
@ -559,6 +703,7 @@ Yanked
|
|||
- `PyObjectProtocol::get_type_ptr()` by ijl in [#242](https://github.com/PyO3/pyo3/pull/242)
|
||||
|
||||
### Changed
|
||||
|
||||
- Removes the types from the root module and the prelude. They now live in `pyo3::types` instead.
|
||||
- All exceptions are consturcted with `py_err` instead of `new`, as they return `PyErr` and not `Self`.
|
||||
- `as_mut` and friends take and `&mut self` instead of `&self`
|
||||
|
@ -572,13 +717,15 @@ Yanked
|
|||
- Starting to use `NonNull<*mut PyObject>` for Py and PyObject by ijl [#260](https://github.com/PyO3/pyo3/pull/260)
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed most entries from the prelude. The new prelude is small and clear.
|
||||
- Slowly removing specialization uses
|
||||
- `PyString`, `PyUnicode`, and `PyBytes` no longer have a `data()` method
|
||||
(replaced by `as_bytes()`) and `PyStringData` has been removed.
|
||||
(replaced by `as_bytes()`) and `PyStringData` has been removed.
|
||||
- The pyobject_extract macro
|
||||
|
||||
### Fixed
|
||||
|
||||
- Added an explanation that the GIL can temporarily be released even while holding a GILGuard.
|
||||
- Lots of clippy errors
|
||||
- Fix segfault on calling an unknown method on a PyObject
|
||||
|
@ -586,38 +733,52 @@ Yanked
|
|||
- Fixed a segfault with subclassing pyo3 create classes and using `__class__` by kngwyu [#263](https://github.com/PyO3/pyo3/pull/263)
|
||||
|
||||
## [0.4.1] - 2018-08-20
|
||||
|
||||
### Changed
|
||||
|
||||
- PyTryFrom's error is always to `PyDowncastError`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed compilation on nightly since `use_extern_macros` was stabilized
|
||||
|
||||
### Removed
|
||||
|
||||
- The pyobject_downcast macro
|
||||
|
||||
## [0.4.0] - 2018-07-30
|
||||
|
||||
### Changed
|
||||
|
||||
- Merged both examples into one
|
||||
- Rustfmt all the things :heavy_check_mark:
|
||||
- Switched to [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
### Removed
|
||||
|
||||
- Conversions from tuples to PyDict due to [rust-lang/rust#52050](https://github.com/rust-lang/rust/issues/52050)
|
||||
|
||||
## [0.3.2] - 2018-07-22
|
||||
|
||||
### Changed
|
||||
|
||||
- Replaced `concat_idents` with mashup
|
||||
|
||||
## [0.3.1] - 2018-07-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed scoping bug in pyobject_native_type that would break rust-numpy
|
||||
|
||||
## [0.3.0] - 2018-07-18
|
||||
|
||||
### Added
|
||||
|
||||
- A few internal macros became part of the public api ([#155](https://github.com/PyO3/pyo3/pull/155), [#186](https://github.com/PyO3/pyo3/pull/186))
|
||||
- Always clone in getters. This allows using the get-annotation on all Clone-Types
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgraded to syn 0.14 which means much better error messages :tada:
|
||||
- 128 bit integer support by [kngwyu](https://github.com/kngwyu) ([#137](https://github.com/PyO3/pyo3/pull/173))
|
||||
- `proc_macro` has been stabilized on nightly ([rust-lang/rust#52081](https://github.com/rust-lang/rust/pull/52081)). This means that we can remove the `proc_macro` feature, but now we need the `use_extern_macros` from the 2018 edition instead.
|
||||
|
@ -627,53 +788,73 @@ Yanked
|
|||
- The guide is now properly versioned.
|
||||
|
||||
## [0.2.7] - 2018-05-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix nightly breakage with proc_macro_path
|
||||
|
||||
## [0.2.6] - 2018-04-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix compatibility with TryFrom trait #137
|
||||
|
||||
## [0.2.5] - 2018-02-21
|
||||
|
||||
### Added
|
||||
|
||||
- CPython 3.7 support
|
||||
|
||||
### Fixed
|
||||
|
||||
- Embedded CPython 3.7b1 crashes on initialization #110
|
||||
- Generated extension functions are weakly typed #108
|
||||
- call_method*() crashes when the method does not exist #113
|
||||
- call_method\*() crashes when the method does not exist #113
|
||||
- Allow importing exceptions from nested modules #116
|
||||
|
||||
## [0.2.4] - 2018-01-19
|
||||
|
||||
### Added
|
||||
|
||||
- Allow to get mutable ref from PyObject #106
|
||||
- Drop `RefFromPyObject` trait
|
||||
- Add Python::register_any() method
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix impl `FromPyObject` for `Py<T>`
|
||||
- Mark method that work with raw pointers as unsafe #95
|
||||
|
||||
## [0.2.3] - 11-27-2017
|
||||
|
||||
### Changed
|
||||
|
||||
- Rustup to 1.23.0-nightly 2017-11-07
|
||||
|
||||
### Fixed
|
||||
|
||||
- Proper `c_char` usage #93
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove use of now unneeded 'AsciiExt' trait
|
||||
|
||||
## [0.2.2] - 09-26-2017
|
||||
|
||||
### Changed
|
||||
|
||||
- Rustup to 1.22.0-nightly 2017-09-30
|
||||
|
||||
## [0.2.1] - 09-26-2017
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix rustc const_fn nightly breakage
|
||||
|
||||
## [0.2.0] - 08-12-2017
|
||||
|
||||
### Added
|
||||
|
||||
- Added inheritance support #15
|
||||
- Added weakref support #56
|
||||
- Added subclass support #64
|
||||
|
@ -683,14 +864,19 @@ Yanked
|
|||
- Introduce IntoPyDictPointer similar to IntoPyTuple #69
|
||||
|
||||
### Changed
|
||||
|
||||
- Allow to add gc support without implementing PyGCProtocol #57
|
||||
- Refactor `PyErr` implementation. Drop `py` parameter from constructor.
|
||||
|
||||
## [0.1.0] - 07-23-2017
|
||||
|
||||
### Added
|
||||
|
||||
- Initial release
|
||||
|
||||
[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.13.2...HEAD
|
||||
[unreleased]: https://github.com/pyo3/pyo3/compare/v0.14.1...HEAD
|
||||
[0.14.1]: https://github.com/pyo3/pyo3/compare/v0.14.0...v0.14.1
|
||||
[0.14.0]: https://github.com/pyo3/pyo3/compare/v0.13.2...v0.14.0
|
||||
[0.13.2]: https://github.com/pyo3/pyo3/compare/v0.13.1...v0.13.2
|
||||
[0.13.1]: https://github.com/pyo3/pyo3/compare/v0.13.0...v0.13.1
|
||||
[0.13.0]: https://github.com/pyo3/pyo3/compare/v0.12.4...v0.13.0
|
||||
|
|
16
Cargo.toml
16
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3"
|
||||
version = "0.13.2"
|
||||
version = "0.14.1"
|
||||
description = "Bindings to Python interpreter"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
readme = "README.md"
|
||||
|
@ -24,14 +24,16 @@ num-bigint = { version = "0.4", optional = true }
|
|||
num-complex = { version = "0.4", optional = true }
|
||||
# must stay at 0.1.x for Rust 1.41 compatibility
|
||||
paste = { version = "0.1.18", optional = true }
|
||||
pyo3-macros = { path = "pyo3-macros", version = "=0.13.2", optional = true }
|
||||
pyo3-macros = { path = "pyo3-macros", version = "=0.14.1", optional = true }
|
||||
unindent = { version = "0.1.4", optional = true }
|
||||
hashbrown = { version = ">= 0.9, < 0.12", optional = true }
|
||||
indexmap = { version = ">= 1.6, < 1.8", optional = true }
|
||||
serde = {version = "1.0", optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
assert_approx_eq = "1.1.0"
|
||||
criterion = "0.3"
|
||||
# O.3.5 uses the matches! macro, which isn't compatible with Rust 1.41
|
||||
criterion = "=0.3.4"
|
||||
trybuild = "1.0.23"
|
||||
rustversion = "1.0"
|
||||
proptest = { version = "0.10.1", default-features = false, features = ["std"] }
|
||||
|
@ -40,7 +42,7 @@ pyo3 = { path = ".", default-features = false, features = ["macros", "auto-initi
|
|||
serde_json = "1.0.61"
|
||||
|
||||
[build-dependencies]
|
||||
pyo3-build-config = { path = "pyo3-build-config", version = "=0.14.0-alpha.0" }
|
||||
pyo3-build-config = { path = "pyo3-build-config", version = "0.14.1" }
|
||||
|
||||
[features]
|
||||
default = ["macros"]
|
||||
|
@ -76,6 +78,10 @@ nightly = []
|
|||
name = "bench_call"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "bench_err"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "bench_dict"
|
||||
harness = false
|
||||
|
@ -117,5 +123,5 @@ members = [
|
|||
|
||||
[package.metadata.docs.rs]
|
||||
no-default-features = true
|
||||
features = ["macros", "num-bigint", "num-complex", "hashbrown", "serde", "multiple-pymethods"]
|
||||
features = ["macros", "num-bigint", "num-complex", "hashbrown", "serde", "multiple-pymethods", "indexmap"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
|
|
@ -30,9 +30,22 @@ Don't be afraid if the solution is not clear to you! The core PyO3 contributors
|
|||
PyO3 has a user guide (using mdbook) as well as the usual Rust API docs. The aim is for both of these to be detailed, easy to understand, and up-to-date. Pull requests are always welcome to fix typos, change wording, add examples, etc.
|
||||
|
||||
There are some specific areas of focus where help is currently needed for the documentation:
|
||||
|
||||
- Issues requesting documentation improvements are tracked with the [documentation](https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+label%3Adocumentation) label.
|
||||
- Not all APIs had docs or examples when they were made. The goal is to have documentation on all PyO3 APIs ([#306](https://github.com/PyO3/pyo3/issues/306)). If you see an API lacking a doc, please write one and open a PR!
|
||||
- Not all `unsafe` APIs had safety notes when they made. We'd like to ensure all `unsafe` APIs are carefully explained ([#698](https://github.com/PyO3/pyo3/issues/698)). If you see an `unsafe` function missing safety notes, please write some and open a PR!
|
||||
|
||||
#### Doctests
|
||||
|
||||
We use lots of code blocks in our docs. Run `cargo test --doc` when making changes to check that
|
||||
the doctests still work, or `cargo test` to run all the tests including doctests. See
|
||||
https://doc.rust-lang.org/rustdoc/documentation-tests.html for a guide on doctests.
|
||||
|
||||
#### Building the guide
|
||||
|
||||
You can preview the user guide by building it locally with `mdbook`.
|
||||
|
||||
First, [install `mdbook`](https://rust-lang.github.io/mdBook/cli/index.html). Then, run
|
||||
`mdbook build -d ../gh-pages-build guide --open`.
|
||||
|
||||
### Help design the next PyO3
|
||||
|
||||
|
@ -92,4 +105,4 @@ At the moment there is no official organisation that accepts sponsorship on PyO3
|
|||
|
||||
In the meanwhile, some of our maintainers have personal Github sponsorship pages and would be grateful for your support:
|
||||
|
||||
* [davidhewitt](https://github.com/sponsors/davidhewitt)
|
||||
- [davidhewitt](https://github.com/sponsors/davidhewitt)
|
||||
|
|
126
README.md
126
README.md
|
@ -1,35 +1,32 @@
|
|||
# PyO3
|
||||
|
||||
[![actions status](https://github.com/PyO3/pyo3/workflows/CI/badge.svg)](https://github.com/PyO3/pyo3/actions)
|
||||
[![benchmark](https://github.com/PyO3/pyo3/actions/workflows/bench.yml/badge.svg)](https://pyo3.rs/dev/bench/)
|
||||
[![codecov](https://codecov.io/gh/PyO3/pyo3/branch/main/graph/badge.svg)](https://codecov.io/gh/PyO3/pyo3)
|
||||
[![crates.io](https://meritbadge.herokuapp.com/pyo3)](https://crates.io/crates/pyo3)
|
||||
[![crates.io](https://img.shields.io/crates/v/pyo3)](https://crates.io/crates/pyo3)
|
||||
[![minimum rustc 1.41](https://img.shields.io/badge/rustc-1.41+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
|
||||
[![dev chat](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/PyO3/Lobby)
|
||||
[![contributing notes](https://img.shields.io/badge/contribute-on%20github-Green)](https://github.com/PyO3/pyo3/blob/main/Contributing.md)
|
||||
|
||||
[Rust](http://www.rust-lang.org/) bindings for [Python](https://www.python.org/), including tools for creating native Python extension modules. Running and interacting with Python code from a Rust binary is also supported.
|
||||
|
||||
* User Guide: [stable](https://pyo3.rs) | [main](https://pyo3.rs/main)
|
||||
- User Guide: [stable](https://pyo3.rs) | [main](https://pyo3.rs/main)
|
||||
|
||||
* API Documentation: [stable](https://docs.rs/pyo3/) | [main](https://pyo3.rs/main/doc)
|
||||
- API Documentation: [stable](https://docs.rs/pyo3/) | [main](https://pyo3.rs/main/doc)
|
||||
|
||||
## Usage
|
||||
|
||||
PyO3 supports Python 3.6 and up. The minimum required Rust version is 1.41.
|
||||
|
||||
PyPy is also supported. Some minor features are unavailable on PyPy - please refer to the [pypy section in the guide](https://pyo3.rs/main/building_and_distribution/pypy.html) for more information.
|
||||
PyPy is also supported. Some minor features are unavailable on PyPy - please refer to the [pypy section in the guide](https://pyo3.rs/latest/building_and_distribution/pypy.html) for more information.
|
||||
|
||||
You can either write a native Python module in Rust, or use Python from a Rust binary.
|
||||
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.
|
||||
|
||||
However, on some OSs, you need some additional packages. E.g. if you are on *Ubuntu 18.04*, please run
|
||||
### Using Rust from Python
|
||||
|
||||
```bash
|
||||
sudo apt install python3-dev python-dev
|
||||
```
|
||||
PyO3 can be used to generate a native Python module. The easiest way to try this out for the first time is to use [`maturin`](https://github.com/PyO3/maturin). `maturin` is a tool for building and publishing Rust-based Python packages with minimal configuration. The following steps set up some files for an example Python module, install `maturin`, and then show how build and import the Python module.
|
||||
|
||||
## Using Rust from Python
|
||||
|
||||
PyO3 can be used to generate a native Python module.
|
||||
First, create a new folder (let's call it `string_sum`) containing the following two files:
|
||||
|
||||
**`Cargo.toml`**
|
||||
|
||||
|
@ -49,7 +46,7 @@ name = "string_sum"
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies.pyo3]
|
||||
version = "0.13.2"
|
||||
version = "0.14.1"
|
||||
features = ["extension-module"]
|
||||
```
|
||||
|
||||
|
@ -57,7 +54,6 @@ features = ["extension-module"]
|
|||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::wrap_pyfunction;
|
||||
|
||||
/// Formats the sum of two numbers as string.
|
||||
#[pyfunction]
|
||||
|
@ -65,27 +61,54 @@ fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
|
|||
Ok((a + b).to_string())
|
||||
}
|
||||
|
||||
/// A Python module implemented in Rust.
|
||||
/// A Python module implemented in Rust. The name of this function must match
|
||||
/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
|
||||
/// import the module.
|
||||
#[pymodule]
|
||||
fn string_sum(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn string_sum(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
While developing, you can symlink (or copy) and rename the shared library from the target folder: On MacOS, rename `libstring_sum.dylib` to `string_sum.so`, on Windows `libstring_sum.dll` to `string_sum.pyd`, and on Linux `libstring_sum.so` to `string_sum.so`. Then open a Python shell in the same folder and you'll be able to `import string_sum`.
|
||||
With those two files in place, now `maturin` needs to be installed. This can be done using Python's package manager `pip`. First, load up a new Python `virtualenv`, and install `maturin` into it:
|
||||
|
||||
To build, test and publish your crate as a Python module, you can use [maturin](https://github.com/PyO3/maturin) or [setuptools-rust](https://github.com/PyO3/setuptools-rust). You can find an example for setuptools-rust in [examples/word-count](https://github.com/PyO3/pyo3/tree/main/examples/word-count), while maturin should work on your crate without any configuration.
|
||||
```bash
|
||||
$ cd string_sum
|
||||
$ python -m venv .env
|
||||
$ source .env/bin/activate
|
||||
$ pip install maturin
|
||||
```
|
||||
|
||||
## Using Python from Rust
|
||||
Now build and execute the module:
|
||||
|
||||
If you want your Rust application to create a Python interpreter internally and
|
||||
use it to run Python code, add `pyo3` to your `Cargo.toml` like this:
|
||||
```bash
|
||||
$ maturin develop
|
||||
# lots of progress output as maturin runs the compilation...
|
||||
$ python
|
||||
>>> import string_sum
|
||||
>>> string_sum.sum_as_string(5, 20)
|
||||
'25'
|
||||
```
|
||||
|
||||
As well as with `maturin`, it is possible to build using [`setuptools-rust`](https://github.com/PyO3/setuptools-rust) or [manually](https://pyo3.rs/latest/building_and_distribution.html#manual-builds). Both offer more flexibility than `maturin` but require further configuration.
|
||||
|
||||
### Using Python from Rust
|
||||
|
||||
To embed Python into a Rust binary, you need to ensure that your Python installation contains a shared library. The following steps demonstrate how to ensure this (for Ubuntu), and then give some example code which runs an embedded Python interpreter.
|
||||
|
||||
To install the Python shared library, if you are on Ubuntu, you can run:
|
||||
|
||||
```bash
|
||||
sudo apt install python3-dev
|
||||
```
|
||||
|
||||
Start a new project with `cargo new`. Next, add `pyo3` to your `Cargo.toml` like this:
|
||||
|
||||
```toml
|
||||
[dependencies.pyo3]
|
||||
version = "0.13.2"
|
||||
version = "0.14.1"
|
||||
features = ["auto-initialize"]
|
||||
```
|
||||
|
||||
|
@ -116,43 +139,46 @@ fn main_(py: Python) -> PyResult<()> {
|
|||
}
|
||||
```
|
||||
|
||||
Our guide has [a section](https://pyo3.rs/main/python_from_rust.html) with lots of examples
|
||||
The guide has [a section](https://pyo3.rs/latest/python_from_rust.html) with lots of examples
|
||||
about this topic.
|
||||
|
||||
## Tools and libraries
|
||||
* [maturin](https://github.com/PyO3/maturin) _Zero configuration build tool for Rust-made Python extensions_.
|
||||
* [setuptools-rust](https://github.com/PyO3/setuptools-rust) _Setuptools plugin for Rust support_.
|
||||
* [pyo3-built](https://github.com/PyO3/pyo3-built) _Simple macro to expose metadata obtained with the [`built`](https://crates.io/crates/built) crate as a [`PyDict`](https://docs.rs/pyo3/0.12.0/pyo3/types/struct.PyDict.html)_
|
||||
* [rust-numpy](https://github.com/PyO3/rust-numpy) _Rust binding of NumPy C-API_
|
||||
* [dict-derive](https://github.com/gperinazzo/dict-derive) _Derive FromPyObject to automatically transform Python dicts into Rust structs_
|
||||
* [pyo3-log](https://github.com/vorner/pyo3-log) _Bridge from Rust to Python logging_
|
||||
* [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
|
||||
|
||||
- [maturin](https://github.com/PyO3/maturin) _Zero configuration build tool for Rust-made Python extensions_.
|
||||
- [setuptools-rust](https://github.com/PyO3/setuptools-rust) _Setuptools plugin for Rust support_.
|
||||
- [pyo3-built](https://github.com/PyO3/pyo3-built) _Simple macro to expose metadata obtained with the [`built`](https://crates.io/crates/built) crate as a [`PyDict`](https://docs.rs/pyo3/0.12.0/pyo3/types/struct.PyDict.html)_
|
||||
- [rust-numpy](https://github.com/PyO3/rust-numpy) _Rust binding of NumPy C-API_
|
||||
- [dict-derive](https://github.com/gperinazzo/dict-derive) _Derive FromPyObject to automatically transform Python dicts into Rust structs_
|
||||
- [pyo3-log](https://github.com/vorner/pyo3-log) _Bridge from Rust to Python logging_
|
||||
- [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
|
||||
|
||||
## Examples
|
||||
|
||||
* [hyperjson](https://github.com/mre/hyperjson) _A hyper-fast Python module for reading/writing JSON data using Rust's serde-json_
|
||||
* [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._
|
||||
* [point-process](https://github.com/ManifoldFR/point-process-rust/tree/master/pylib) _High level API for pointprocesses as a Python library_
|
||||
* [autopy](https://github.com/autopilot-rs/autopy) _A simple, cross-platform GUI automation library for Python and Rust._
|
||||
* Contains an example of building wheels on TravisCI and appveyor using [cibuildwheel](https://github.com/joerick/cibuildwheel)
|
||||
* [orjson](https://github.com/ijl/orjson) _Fast Python JSON library_
|
||||
* [inline-python](https://github.com/dronesforwork/inline-python) _Inline Python code directly in your Rust code_
|
||||
* [Rogue-Gym](https://github.com/kngwyu/rogue-gym) _Customizable rogue-like game for AI experiments_
|
||||
* Contains an example of building wheels on Azure Pipelines
|
||||
* [fastuuid](https://github.com/thedrow/fastuuid/) _Python bindings to Rust's UUID library_
|
||||
* [wasmer-python](https://github.com/wasmerio/wasmer-python) _Python library to run WebAssembly binaries_
|
||||
* [mocpy](https://github.com/cds-astro/mocpy) _Astronomical Python library offering data structures for describing any arbitrary coverage regions on the unit sphere_
|
||||
* [tokenizers](https://github.com/huggingface/tokenizers/tree/master/bindings/python) _Python bindings to the Hugging Face tokenizers (NLP) written in Rust_
|
||||
* [pyre](https://github.com/Project-Dream-Weaver/Pyre) _Fast Python HTTP server written in Rust_
|
||||
* [jsonschema-rs](https://github.com/Stranger6667/jsonschema-rs/tree/master/bindings/python) _Fast JSON Schema validation library_
|
||||
* [css-inline](https://github.com/Stranger6667/css-inline/tree/master/bindings/python) _CSS inlining for Python implemented in Rust_
|
||||
* [cryptography](https://github.com/pyca/cryptography/tree/main/src/rust) _Python cryptography library with some functionality in Rust_
|
||||
* [polaroid](https://github.com/daggy1234/polaroid) _Hyper Fast and safe image manipulation library for Python written in Rust_
|
||||
- [hyperjson](https://github.com/mre/hyperjson) _A hyper-fast Python module for reading/writing JSON data using Rust's serde-json_
|
||||
- [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._
|
||||
- [point-process](https://github.com/ManifoldFR/point-process-rust/tree/master/pylib) _High level API for pointprocesses as a Python library_
|
||||
- [autopy](https://github.com/autopilot-rs/autopy) _A simple, cross-platform GUI automation library for Python and Rust._
|
||||
- Contains an example of building wheels on TravisCI and appveyor using [cibuildwheel](https://github.com/joerick/cibuildwheel)
|
||||
- [orjson](https://github.com/ijl/orjson) _Fast Python JSON library_
|
||||
- [inline-python](https://github.com/dronesforwork/inline-python) _Inline Python code directly in your Rust code_
|
||||
- [Rogue-Gym](https://github.com/kngwyu/rogue-gym) _Customizable rogue-like game for AI experiments_
|
||||
- Contains an example of building wheels on Azure Pipelines
|
||||
- [fastuuid](https://github.com/thedrow/fastuuid/) _Python bindings to Rust's UUID library_
|
||||
- [wasmer-python](https://github.com/wasmerio/wasmer-python) _Python library to run WebAssembly binaries_
|
||||
- [mocpy](https://github.com/cds-astro/mocpy) _Astronomical Python library offering data structures for describing any arbitrary coverage regions on the unit sphere_
|
||||
- [tokenizers](https://github.com/huggingface/tokenizers/tree/master/bindings/python) _Python bindings to the Hugging Face tokenizers (NLP) written in Rust_
|
||||
- [pyre](https://github.com/Project-Dream-Weaver/Pyre) _Fast Python HTTP server written in Rust_
|
||||
- [jsonschema-rs](https://github.com/Stranger6667/jsonschema-rs/tree/master/bindings/python) _Fast JSON Schema validation library_
|
||||
- [css-inline](https://github.com/Stranger6667/css-inline/tree/master/bindings/python) _CSS inlining for Python implemented in Rust_
|
||||
- [cryptography](https://github.com/pyca/cryptography/tree/main/src/rust) _Python cryptography library with some functionality in Rust_
|
||||
- [polaroid](https://github.com/daggy1234/polaroid) _Hyper Fast and safe image manipulation library for Python written in Rust_
|
||||
- [ormsgpack](https://github.com/aviramha/ormsgpack) _Fast Python msgpack library_
|
||||
|
||||
## Contributing
|
||||
|
||||
Everyone is welcomed to contribute to PyO3! There are many ways to support the project, such as:
|
||||
|
||||
- help PyO3 users with issues on Github and Gitter
|
||||
- improve documentation
|
||||
- write features and bugfixes
|
||||
|
@ -162,7 +188,7 @@ Our [contributing notes](https://github.com/PyO3/pyo3/blob/main/Contributing.md)
|
|||
|
||||
If you don't have time to contribute yourself but still wish to support the project's future success, some of our maintainers have Github sponsorship pages:
|
||||
|
||||
* [davidhewitt](https://github.com/sponsors/davidhewitt)
|
||||
- [davidhewitt](https://github.com/sponsors/davidhewitt)
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -18,11 +18,11 @@ fn bench_call_0(b: &mut Bencher) {
|
|||
"#
|
||||
);
|
||||
|
||||
let foo = module.getattr("foo").unwrap();
|
||||
let foo_module = module.getattr("foo").unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..1000 {
|
||||
foo.call0().unwrap();
|
||||
foo_module.call0().unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
|
@ -38,11 +38,11 @@ fn bench_call_method_0(b: &mut Bencher) {
|
|||
"#
|
||||
);
|
||||
|
||||
let foo = module.getattr("Foo").unwrap().call0().unwrap();
|
||||
let foo_module = module.getattr("Foo").unwrap().call0().unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..1000 {
|
||||
foo.call_method0("foo").unwrap();
|
||||
foo_module.call_method0("foo").unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
|
@ -18,6 +18,13 @@ fn iter_dict(b: &mut Bencher) {
|
|||
});
|
||||
}
|
||||
|
||||
fn dict_new(b: &mut Bencher) {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
const LEN: usize = 50_000;
|
||||
b.iter(|| (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py));
|
||||
}
|
||||
|
||||
fn dict_get_item(b: &mut Bencher) {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
|
@ -58,6 +65,7 @@ fn extract_hashbrown_map(b: &mut Bencher) {
|
|||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("iter_dict", iter_dict);
|
||||
c.bench_function("dict_new", dict_new);
|
||||
c.bench_function("dict_get_item", dict_get_item);
|
||||
c.bench_function("extract_hashmap", extract_hashmap);
|
||||
c.bench_function("extract_btreemap", extract_btreemap);
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
use criterion::{criterion_group, criterion_main, Bencher, Criterion};
|
||||
|
||||
use pyo3::{exceptions::PyValueError, prelude::*};
|
||||
|
||||
fn err_new_restore_and_fetch(b: &mut Bencher) {
|
||||
Python::with_gil(|py| {
|
||||
b.iter(|| {
|
||||
PyValueError::new_err("some exception message").restore(py);
|
||||
PyErr::fetch(py)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn err_new_without_gil(b: &mut Bencher) {
|
||||
b.iter(|| PyValueError::new_err("some exception message"))
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("err_new_restore_and_fetch", err_new_restore_and_fetch);
|
||||
c.bench_function("err_new_without_gil", err_new_without_gil);
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
|
@ -17,6 +17,13 @@ fn iter_list(b: &mut Bencher) {
|
|||
});
|
||||
}
|
||||
|
||||
fn list_new(b: &mut Bencher) {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
const LEN: usize = 50_000;
|
||||
b.iter(|| PyList::new(py, 0..LEN));
|
||||
}
|
||||
|
||||
fn list_get_item(b: &mut Bencher) {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
|
@ -32,6 +39,7 @@ fn list_get_item(b: &mut Bencher) {
|
|||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("iter_list", iter_list);
|
||||
c.bench_function("list_new", list_new);
|
||||
c.bench_function("list_get_item", list_get_item);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,13 @@ fn iter_tuple(b: &mut Bencher) {
|
|||
});
|
||||
}
|
||||
|
||||
fn tuple_new(b: &mut Bencher) {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
const LEN: usize = 50_000;
|
||||
b.iter(|| PyTuple::new(py, 0..LEN));
|
||||
}
|
||||
|
||||
fn tuple_get_item(b: &mut Bencher) {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
|
@ -32,6 +39,7 @@ fn tuple_get_item(b: &mut Bencher) {
|
|||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("iter_tuple", iter_tuple);
|
||||
c.bench_function("tuple_new", tuple_new);
|
||||
c.bench_function("tuple_get_item", tuple_get_item);
|
||||
}
|
||||
|
||||
|
|
68
build.rs
68
build.rs
|
@ -1,23 +1,14 @@
|
|||
use std::{env, process::Command};
|
||||
|
||||
use pyo3_build_config::{InterpreterConfig, PythonImplementation, PythonVersion};
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
use pyo3_build_config::{
|
||||
bail, ensure,
|
||||
errors::{Context, Result},
|
||||
InterpreterConfig, PythonImplementation, PythonVersion,
|
||||
};
|
||||
|
||||
/// Minimum Python version PyO3 supports.
|
||||
const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 6 };
|
||||
|
||||
// A simple macro for returning an error. Resembles anyhow::bail.
|
||||
macro_rules! bail {
|
||||
($msg: expr) => { return Err($msg.into()); };
|
||||
($fmt: literal $($args: tt)+) => { return Err(format!($fmt $($args)+).into()); };
|
||||
}
|
||||
|
||||
// A simple macro for checking a condition. Resembles anyhow::ensure.
|
||||
macro_rules! ensure {
|
||||
($condition:expr, $($args: tt)+) => { if !($condition) { bail!($($args)+) } };
|
||||
}
|
||||
|
||||
fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> {
|
||||
ensure!(
|
||||
interpreter_config.version >= MINIMUM_SUPPORTED_VERSION,
|
||||
|
@ -87,9 +78,7 @@ fn get_rustc_link_lib(config: &InterpreterConfig) -> Result<String> {
|
|||
match config.implementation {
|
||||
PythonImplementation::CPython => match &config.ld_version {
|
||||
Some(ld_version) => format!("python{}", ld_version),
|
||||
None => {
|
||||
return Err("failed to configure `ld_version` when compiling for unix".into())
|
||||
}
|
||||
None => bail!("failed to configure `ld_version` when compiling for unix"),
|
||||
},
|
||||
PythonImplementation::PyPy => format!("pypy{}-c", config.version.major),
|
||||
}
|
||||
|
@ -119,7 +108,7 @@ fn emit_cargo_configuration(interpreter_config: &InterpreterConfig) -> Result<()
|
|||
match (is_extension_module, target_os.as_str()) {
|
||||
(_, "windows") => {
|
||||
// always link on windows, even with extension module
|
||||
println!("{}", get_rustc_link_lib(&interpreter_config)?);
|
||||
println!("{}", get_rustc_link_lib(interpreter_config)?);
|
||||
// Set during cross-compiling.
|
||||
if let Some(libdir) = &interpreter_config.libdir {
|
||||
println!("cargo:rustc-link-search=native={}", libdir);
|
||||
|
@ -137,7 +126,7 @@ fn emit_cargo_configuration(interpreter_config: &InterpreterConfig) -> Result<()
|
|||
(false, _) | (_, "android") => {
|
||||
// other systems, only link libs if not extension module
|
||||
// android always link.
|
||||
println!("{}", get_rustc_link_lib(&interpreter_config)?);
|
||||
println!("{}", get_rustc_link_lib(interpreter_config)?);
|
||||
if let Some(libdir) = &interpreter_config.libdir {
|
||||
println!("cargo:rustc-link-search=native={}", libdir);
|
||||
}
|
||||
|
@ -157,7 +146,7 @@ fn emit_cargo_configuration(interpreter_config: &InterpreterConfig) -> Result<()
|
|||
|
||||
if env::var_os("CARGO_FEATURE_AUTO_INITIALIZE").is_some() {
|
||||
if !interpreter_config.shared {
|
||||
return Err(format!(
|
||||
bail!(
|
||||
"The `auto-initialize` feature is enabled, but your python installation only supports \
|
||||
embedding the Python interpreter statically. If you are attempting to run tests, or a \
|
||||
binary which is okay to link dynamically, install a Python distribution which ships \
|
||||
|
@ -170,40 +159,69 @@ fn emit_cargo_configuration(interpreter_config: &InterpreterConfig) -> Result<()
|
|||
https://pyo3.rs/v{pyo3_version}/\
|
||||
building_and_distribution.html#embedding-python-in-rust",
|
||||
pyo3_version = env::var("CARGO_PKG_VERSION").unwrap()
|
||||
)
|
||||
.into());
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: PYO3_CI env is a hack to workaround CI with PyPy, where the `dev-dependencies`
|
||||
// currently cause `auto-initialize` to be enabled in CI.
|
||||
// Once cargo's `resolver = "2"` is stable (~ MSRV Rust 1.52), remove this.
|
||||
if interpreter_config.is_pypy() && env::var_os("PYO3_CI").is_none() {
|
||||
return Err("The `auto-initialize` feature is not supported with PyPy.".into());
|
||||
bail!("The `auto-initialize` feature is not supported with PyPy.");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generates the interpreter config suitable for the host / target / cross-compilation at hand.
|
||||
///
|
||||
/// The result is written to pyo3_build_config::PATH, which downstream scripts can read from
|
||||
/// (including `pyo3-macros-backend` during macro expansion).
|
||||
fn configure_pyo3() -> Result<()> {
|
||||
let interpreter_config = pyo3_build_config::get();
|
||||
let interpreter_config = pyo3_build_config::make_interpreter_config()?;
|
||||
ensure_python_version(&interpreter_config)?;
|
||||
ensure_target_architecture(&interpreter_config)?;
|
||||
emit_cargo_configuration(&interpreter_config)?;
|
||||
interpreter_config.to_writer(
|
||||
&mut std::fs::File::create(pyo3_build_config::PATH).with_context(|| {
|
||||
format!(
|
||||
"failed to create config file at {}",
|
||||
pyo3_build_config::PATH
|
||||
)
|
||||
})?,
|
||||
)?;
|
||||
interpreter_config.emit_pyo3_cfgs();
|
||||
|
||||
let rustc_minor_version = rustc_minor_version().unwrap_or(0);
|
||||
|
||||
// Enable use of const generics on Rust 1.51 and greater
|
||||
if rustc_minor_version().unwrap_or(0) >= 51 {
|
||||
if rustc_minor_version >= 51 {
|
||||
println!("cargo:rustc-cfg=min_const_generics");
|
||||
}
|
||||
|
||||
// Enable use of std::ptr::addr_of! on Rust 1.51 and greater
|
||||
if rustc_minor_version >= 51 {
|
||||
println!("cargo:rustc-cfg=addr_of");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Print out error messages using display, to get nicer formatting.
|
||||
if let Err(e) = configure_pyo3() {
|
||||
use std::error::Error;
|
||||
eprintln!("error: {}", e);
|
||||
let mut source = e.source();
|
||||
if source.is_some() {
|
||||
eprintln!("caused by:");
|
||||
let mut index = 0;
|
||||
while let Some(some_source) = source {
|
||||
eprintln!(" - {}: {}", index, some_source);
|
||||
source = some_source.source();
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,3 +7,6 @@ coverage:
|
|||
target: auto
|
||||
# Allow a tiny drop of overall project coverage in PR to reduce spurious failures.
|
||||
threshold: 0.25%
|
||||
|
||||
ignore:
|
||||
- tests/*.rs
|
||||
|
|
|
@ -1,39 +1,78 @@
|
|||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyDict, PyTuple};
|
||||
use pyo3::wrap_pyfunction;
|
||||
|
||||
#[pyfunction]
|
||||
fn none() {}
|
||||
|
||||
#[pyfunction(b = "\"bar\"", "*", c = "None")]
|
||||
fn simple<'a>(a: i32, b: &'a str, c: Option<&'a PyDict>) -> (i32, &'a str, Option<&'a PyDict>) {
|
||||
(a, b, c)
|
||||
}
|
||||
|
||||
#[pyfunction(b = "\"bar\"", args = "*", c = "None")]
|
||||
fn simple_args<'a>(
|
||||
a: i32,
|
||||
b: &'a str,
|
||||
c: Option<&'a PyDict>,
|
||||
args: &'a PyTuple,
|
||||
) -> (i32, &'a str, &'a PyTuple, Option<&'a PyDict>) {
|
||||
(a, b, args, c)
|
||||
}
|
||||
|
||||
#[pyfunction(b = "\"bar\"", c = "None", kwargs = "**")]
|
||||
fn simple_kwargs<'a>(
|
||||
a: i32,
|
||||
b: &'a str,
|
||||
c: Option<&'a PyDict>,
|
||||
kwargs: Option<&'a PyDict>,
|
||||
) -> (i32, &'a str, Option<&'a PyDict>, Option<&'a PyDict>) {
|
||||
(a, b, c, kwargs)
|
||||
}
|
||||
|
||||
#[pyfunction(a, b = "\"bar\"", args = "*", c = "None", kwargs = "**")]
|
||||
fn simple_args_kwargs<'a>(
|
||||
a: i32,
|
||||
b: &'a str,
|
||||
args: &'a PyTuple,
|
||||
c: Option<&'a PyDict>,
|
||||
kwargs: Option<&'a PyDict>,
|
||||
) -> (
|
||||
i32,
|
||||
&'a str,
|
||||
&'a PyTuple,
|
||||
Option<&'a PyDict>,
|
||||
Option<&'a PyDict>,
|
||||
) {
|
||||
(a, b, args, c, kwargs)
|
||||
}
|
||||
|
||||
#[pyfunction(args = "*", kwargs = "**")]
|
||||
fn args_and_kwargs<'a>(
|
||||
fn args_kwargs<'a>(
|
||||
args: &'a PyTuple,
|
||||
kwargs: Option<&'a PyDict>,
|
||||
) -> (&'a PyTuple, Option<&'a PyDict>) {
|
||||
(args, kwargs)
|
||||
}
|
||||
|
||||
#[pyfunction(a, b = 2, args = "*", c = 4, kwargs = "**")]
|
||||
fn mixed_args<'a>(
|
||||
a: i32,
|
||||
b: i32,
|
||||
args: &'a PyTuple,
|
||||
c: i32,
|
||||
kwargs: Option<&'a PyDict>,
|
||||
) -> (i32, i32, &'a PyTuple, i32, Option<&'a PyDict>) {
|
||||
(a, b, args, c, kwargs)
|
||||
}
|
||||
#[pyclass]
|
||||
struct EmptyClass {}
|
||||
|
||||
#[pyfunction]
|
||||
fn no_args() {}
|
||||
|
||||
#[pyfunction(b = 2, "*", c = 4)]
|
||||
fn simple_args(a: i32, b: i32, c: i32) -> (i32, i32, i32) {
|
||||
(a, b, c)
|
||||
#[pymethods]
|
||||
impl EmptyClass {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
EmptyClass {}
|
||||
}
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
fn _pyo3_benchmarks(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(args_and_kwargs, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(mixed_args, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(no_args, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(none, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(simple, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(simple_args, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(simple_kwargs, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(simple_args_kwargs, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(args_kwargs, m)?)?;
|
||||
m.add_class::<EmptyClass>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,61 +1,103 @@
|
|||
import pyo3_benchmarks
|
||||
|
||||
|
||||
def test_args_and_kwargs(benchmark):
|
||||
benchmark(pyo3_benchmarks.args_and_kwargs, 1, 2, 3, a=4, foo=10)
|
||||
|
||||
|
||||
def args_and_kwargs_py(*args, **kwargs):
|
||||
return (args, kwargs)
|
||||
|
||||
|
||||
def test_args_and_kwargs_py(benchmark):
|
||||
rust = pyo3_benchmarks.args_and_kwargs(1, 2, 3, bar=4, foo=10)
|
||||
py = args_and_kwargs_py(1, 2, 3, bar=4, foo=10)
|
||||
assert rust == py
|
||||
benchmark(args_and_kwargs_py, 1, 2, 3, bar=4, foo=10)
|
||||
|
||||
|
||||
def test_mixed_args(benchmark):
|
||||
benchmark(pyo3_benchmarks.mixed_args, 1, 2, 3, bar=4, foo=10)
|
||||
|
||||
|
||||
def mixed_args_py(a, b=2, *args, c=4, **kwargs):
|
||||
return (a, b, args, c, kwargs)
|
||||
|
||||
|
||||
def test_mixed_args_py(benchmark):
|
||||
rust = pyo3_benchmarks.mixed_args(1, 2, 3, bar=4, foo=10)
|
||||
py = mixed_args_py(1, 2, 3, bar=4, foo=10)
|
||||
assert rust == py
|
||||
benchmark(mixed_args_py, 1, 2, 3, bar=4, foo=10)
|
||||
|
||||
|
||||
def test_no_args(benchmark):
|
||||
benchmark(pyo3_benchmarks.no_args)
|
||||
|
||||
|
||||
def no_args_py():
|
||||
def none_py():
|
||||
return None
|
||||
|
||||
|
||||
def test_no_args_py(benchmark):
|
||||
rust = pyo3_benchmarks.no_args()
|
||||
py = no_args_py()
|
||||
def test_none_py(benchmark):
|
||||
benchmark(none_py)
|
||||
|
||||
|
||||
def test_none_rs(benchmark):
|
||||
rust = pyo3_benchmarks.none()
|
||||
py = none_py()
|
||||
assert rust == py
|
||||
benchmark(no_args_py)
|
||||
benchmark(pyo3_benchmarks.none)
|
||||
|
||||
|
||||
def test_simple_args(benchmark):
|
||||
benchmark(pyo3_benchmarks.simple_args, 1, 3, c=5)
|
||||
|
||||
|
||||
def simple_args_py(a, b=2, *, c=4):
|
||||
def simple_py(a, b="bar", *, c=None):
|
||||
return a, b, c
|
||||
|
||||
|
||||
def test_simple_args_py(benchmark):
|
||||
rust = pyo3_benchmarks.simple_args(1, 3, c=5)
|
||||
py = simple_args_py(1, 3, c=5)
|
||||
def test_simple_py(benchmark):
|
||||
benchmark(simple_py, 1, "foo", c={1: 2})
|
||||
|
||||
|
||||
def test_simple_rs(benchmark):
|
||||
rust = pyo3_benchmarks.simple(1, "foo", c={1: 2})
|
||||
py = simple_py(1, "foo", c={1: 2})
|
||||
assert rust == py
|
||||
benchmark(simple_args_py, 1, 3, c=5)
|
||||
benchmark(pyo3_benchmarks.simple, 1, "foo", c={1: 2})
|
||||
|
||||
|
||||
def simple_args_py(a, b="bar", *args, c=None):
|
||||
return a, b, args, c
|
||||
|
||||
|
||||
def test_simple_args_py(benchmark):
|
||||
benchmark(simple_args_py, 1, "foo", 4, 5, 6, c={1: 2})
|
||||
|
||||
|
||||
def test_simple_args_rs(benchmark):
|
||||
rust = pyo3_benchmarks.simple_args(1, "foo", 4, 5, 6, c={1: 2})
|
||||
py = simple_args_py(1, "foo", 4, 5, 6, c={1: 2})
|
||||
assert rust == py
|
||||
benchmark(pyo3_benchmarks.simple_args, 1, "foo", 4, 5, 6, c={1: 2})
|
||||
|
||||
|
||||
def simple_kwargs_py(a, b="bar", c=None, **kwargs):
|
||||
return a, b, c, kwargs
|
||||
|
||||
|
||||
def test_simple_kwargs_py(benchmark):
|
||||
benchmark(simple_kwargs_py, 1, "foo", c={1: 2}, bar=4, foo=10)
|
||||
|
||||
|
||||
def test_simple_kwargs_rs(benchmark):
|
||||
rust = pyo3_benchmarks.simple_kwargs(1, "foo", c={1: 2}, bar=4, foo=10)
|
||||
py = simple_kwargs_py(1, "foo", c={1: 2}, bar=4, foo=10)
|
||||
assert rust == py
|
||||
benchmark(pyo3_benchmarks.simple_kwargs, 1, "foo", c={1: 2}, bar=4, foo=10)
|
||||
|
||||
|
||||
def simple_args_kwargs_py(a, b="bar", *args, c=None, **kwargs):
|
||||
return (a, b, args, c, kwargs)
|
||||
|
||||
|
||||
def test_simple_args_kwargs_py(benchmark):
|
||||
benchmark(simple_args_kwargs_py, 1, "foo", "baz", bar=4, foo=10)
|
||||
|
||||
|
||||
def test_simple_args_kwargs_rs(benchmark):
|
||||
rust = pyo3_benchmarks.simple_args_kwargs(1, "foo", "baz", bar=4, foo=10)
|
||||
py = simple_args_kwargs_py(1, "foo", "baz", bar=4, foo=10)
|
||||
assert rust == py
|
||||
benchmark(pyo3_benchmarks.simple_args_kwargs, 1, "foo", "baz", bar=4, foo=10)
|
||||
|
||||
|
||||
def args_kwargs_py(*args, **kwargs):
|
||||
return (args, kwargs)
|
||||
|
||||
|
||||
def test_args_kwargs_py(benchmark):
|
||||
benchmark(args_kwargs_py, 1, "foo", {1: 2}, bar=4, foo=10)
|
||||
|
||||
|
||||
def test_args_kwargs_rs(benchmark):
|
||||
rust = pyo3_benchmarks.args_kwargs(1, "foo", {1: 2}, bar=4, foo=10)
|
||||
py = args_kwargs_py(1, "foo", {1: 2}, bar=4, foo=10)
|
||||
assert rust == py
|
||||
benchmark(pyo3_benchmarks.args_kwargs, 1, "foo", {1: 2}, a=4, foo=10)
|
||||
|
||||
|
||||
def test_empty_class_init(benchmark):
|
||||
benchmark(pyo3_benchmarks.EmptyClass)
|
||||
|
||||
|
||||
class EmptyClassPy:
|
||||
pass
|
||||
|
||||
|
||||
def test_empty_class_init_py(benchmark):
|
||||
benchmark(EmptyClassPy)
|
||||
|
|
|
@ -7,4 +7,4 @@ description = Run the unit tests under {basepython}
|
|||
deps = -rrequirements-dev.txt
|
||||
commands =
|
||||
python setup.py install
|
||||
pytest {posargs}
|
||||
pytest --benchmark-sort=name {posargs}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//! Objects related to PyBuffer and PyStr
|
||||
use pyo3::buffer::PyBuffer;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyBytes, PyString};
|
||||
|
||||
|
@ -14,12 +15,12 @@ impl BytesExtractor {
|
|||
}
|
||||
|
||||
pub fn from_bytes(&mut self, bytes: &PyBytes) -> PyResult<usize> {
|
||||
let byte_vec: Vec<u8> = bytes.extract().unwrap();
|
||||
let byte_vec: Vec<u8> = bytes.extract()?;
|
||||
Ok(byte_vec.len())
|
||||
}
|
||||
|
||||
pub fn from_str(&mut self, string: &PyString) -> PyResult<usize> {
|
||||
let rust_string: String = string.extract().unwrap();
|
||||
let rust_string: String = string.extract()?;
|
||||
Ok(rust_string.len())
|
||||
}
|
||||
|
||||
|
@ -27,6 +28,11 @@ impl BytesExtractor {
|
|||
let rust_string_lossy: String = string.to_string_lossy().to_string();
|
||||
Ok(rust_string_lossy.len())
|
||||
}
|
||||
|
||||
pub fn from_buffer(&mut self, buf: &PyAny) -> PyResult<usize> {
|
||||
let buf = PyBuffer::<u8>::get(buf)?;
|
||||
Ok(buf.item_count())
|
||||
}
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
|
|
|
@ -3,7 +3,6 @@ use pyo3::types::{
|
|||
PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, PyTuple,
|
||||
PyTzInfo,
|
||||
};
|
||||
use pyo3::wrap_pyfunction;
|
||||
|
||||
#[pyfunction]
|
||||
fn make_date(py: Python, year: i32, month: u8, day: u8) -> PyResult<&PyDate> {
|
||||
|
|
|
@ -8,6 +8,7 @@ pub mod dict_iter;
|
|||
pub mod misc;
|
||||
pub mod objstore;
|
||||
pub mod othermod;
|
||||
pub mod path;
|
||||
pub mod pyclass_iter;
|
||||
pub mod subclassing;
|
||||
|
||||
|
@ -17,6 +18,7 @@ use dict_iter::*;
|
|||
use misc::*;
|
||||
use objstore::*;
|
||||
use othermod::*;
|
||||
use path::*;
|
||||
use pyclass_iter::*;
|
||||
use subclassing::*;
|
||||
|
||||
|
@ -28,6 +30,7 @@ fn pyo3_pytests(py: Python, m: &PyModule) -> PyResult<()> {
|
|||
m.add_wrapped(wrap_pymodule!(misc))?;
|
||||
m.add_wrapped(wrap_pymodule!(objstore))?;
|
||||
m.add_wrapped(wrap_pymodule!(othermod))?;
|
||||
m.add_wrapped(wrap_pymodule!(path))?;
|
||||
m.add_wrapped(wrap_pymodule!(pyclass_iter))?;
|
||||
m.add_wrapped(wrap_pymodule!(subclassing))?;
|
||||
|
||||
|
@ -42,6 +45,7 @@ fn pyo3_pytests(py: Python, m: &PyModule) -> PyResult<()> {
|
|||
sys_modules.set_item("pyo3_pytests.misc", m.getattr("misc")?)?;
|
||||
sys_modules.set_item("pyo3_pytests.objstore", m.getattr("objstore")?)?;
|
||||
sys_modules.set_item("pyo3_pytests.othermod", m.getattr("othermod")?)?;
|
||||
sys_modules.set_item("pyo3_pytests.path", m.getattr("path")?)?;
|
||||
sys_modules.set_item("pyo3_pytests.pyclass_iter", m.getattr("pyclass_iter")?)?;
|
||||
sys_modules.set_item("pyo3_pytests.subclassing", m.getattr("subclassing")?)?;
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use pyo3::prelude::*;
|
||||
use pyo3::wrap_pyfunction;
|
||||
|
||||
#[pyfunction]
|
||||
fn issue_219() {
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
//! The code below just tries to use the most important code generation paths
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::wrap_pyfunction;
|
||||
|
||||
#[pyclass]
|
||||
pub struct ModClass {
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
use pyo3::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[pyfunction]
|
||||
fn make_path() -> PathBuf {
|
||||
Path::new("/root").to_owned()
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn take_pathbuf(path: PathBuf) -> PathBuf {
|
||||
path
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
fn path(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(make_path, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(take_pathbuf, m)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,51 +1,29 @@
|
|||
import gc
|
||||
import os
|
||||
import platform
|
||||
|
||||
import psutil
|
||||
import pytest
|
||||
from pyo3_pytests.buf_and_str import BytesExtractor
|
||||
|
||||
PYPY = platform.python_implementation() == "PyPy"
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
PYPY,
|
||||
reason="PyPy has a segfault bug around this area."
|
||||
"See https://github.com/PyO3/pyo3/issues/589 for detail.",
|
||||
)
|
||||
def test_pybuffer_doesnot_leak_memory():
|
||||
N = 10000
|
||||
def test_extract_bytes():
|
||||
extractor = BytesExtractor()
|
||||
process = psutil.Process(os.getpid())
|
||||
message = b'\\(-"-;) A message written in bytes'
|
||||
assert extractor.from_bytes(message) == len(message)
|
||||
|
||||
def memory_diff(f):
|
||||
before = process.memory_info().rss
|
||||
gc.collect() # Trigger Garbage collection
|
||||
for _ in range(N):
|
||||
f()
|
||||
gc.collect() # Trigger Garbage collection
|
||||
after = process.memory_info().rss
|
||||
return after - before
|
||||
|
||||
message_b = b'\\(-"-;) Praying that memory leak would not happen..'
|
||||
message_s = '\\(-"-;) Praying that memory leak would not happen..'
|
||||
message_surrogate = '\\(-"-;) Praying that memory leak would not happen.. \ud800'
|
||||
def test_extract_str():
|
||||
extractor = BytesExtractor()
|
||||
message = '\\(-"-;) A message written as a string'
|
||||
assert extractor.from_str(message) == len(message)
|
||||
|
||||
def from_bytes():
|
||||
extractor.from_bytes(message_b)
|
||||
|
||||
def from_str():
|
||||
extractor.from_str(message_s)
|
||||
def test_extract_str_lossy():
|
||||
extractor = BytesExtractor()
|
||||
message = '\\(-"-;) A message written with a trailing surrogate \ud800'
|
||||
rust_surrogate_len = extractor.from_str_lossy("\ud800")
|
||||
assert extractor.from_str_lossy(message) == len(message) - 1 + rust_surrogate_len
|
||||
|
||||
def from_str_lossy():
|
||||
extractor.from_str_lossy(message_surrogate)
|
||||
|
||||
# Running the memory_diff to warm-up the garbage collector
|
||||
memory_diff(from_bytes)
|
||||
memory_diff(from_str)
|
||||
memory_diff(from_str_lossy)
|
||||
def test_extract_buffer():
|
||||
extractor = BytesExtractor()
|
||||
message = b'\\(-"-;) A message written in bytes'
|
||||
assert extractor.from_buffer(message) == len(message)
|
||||
|
||||
assert memory_diff(from_bytes) == 0
|
||||
assert memory_diff(from_str) == 0
|
||||
assert memory_diff(from_str_lossy) == 0
|
||||
arr = bytearray(b'\\(-"-;) A message written in bytes')
|
||||
assert extractor.from_buffer(arr) == len(arr)
|
||||
|
|
|
@ -3,22 +3,23 @@ import platform
|
|||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from pyo3_pytests.objstore import ObjStore
|
||||
|
||||
PYPY = platform.python_implementation() == "PyPy"
|
||||
|
||||
|
||||
@pytest.mark.skipif(PYPY, reason="PyPy does not have sys.getrefcount")
|
||||
def test_objstore_doesnot_leak_memory():
|
||||
N = 10000
|
||||
message = b'\\(-"-;) Praying that memory leak would not happen..'
|
||||
before = sys.getrefcount(message)
|
||||
|
||||
# PyPy does not have sys.getrefcount, provide a no-op lambda and don't
|
||||
# check refcount on PyPy
|
||||
getrefcount = getattr(sys, "getrefcount", lambda obj: 0)
|
||||
|
||||
before = getrefcount(message)
|
||||
store = ObjStore()
|
||||
for _ in range(N):
|
||||
store.push(message)
|
||||
del store
|
||||
gc.collect()
|
||||
after = sys.getrefcount(message)
|
||||
after = getrefcount(message)
|
||||
|
||||
assert after - before == 0
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import pathlib
|
||||
|
||||
import pyo3_pytests.path as rpath
|
||||
|
||||
|
||||
def test_make_path():
|
||||
p = rpath.make_path()
|
||||
assert p == "/root"
|
||||
|
||||
|
||||
def test_take_pathbuf():
|
||||
p = "/root"
|
||||
assert rpath.take_pathbuf(p) == p
|
||||
|
||||
|
||||
def test_take_pathlib():
|
||||
p = pathlib.Path("/root")
|
||||
assert rpath.take_pathbuf(p) == str(p)
|
|
@ -2,8 +2,6 @@ import platform
|
|||
|
||||
from pyo3_pytests.subclassing import Subclassable
|
||||
|
||||
PYPY = platform.python_implementation() == "PyPy"
|
||||
|
||||
|
||||
class SomeSubClass(Subclassable):
|
||||
def __str__(self):
|
||||
|
@ -11,7 +9,6 @@ class SomeSubClass(Subclassable):
|
|||
|
||||
|
||||
def test_subclassing():
|
||||
if not PYPY:
|
||||
a = SomeSubClass()
|
||||
assert str(a) == "SomeSubclass"
|
||||
assert type(a) is SomeSubClass
|
||||
a = SomeSubClass()
|
||||
assert str(a) == "SomeSubclass"
|
||||
assert type(a) is SomeSubClass
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// https://github.com/tildeio/helix-website/blob/master/crates/word_count/src/lib.rs
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::wrap_pyfunction;
|
||||
use rayon::prelude::*;
|
||||
|
||||
/// Searches for the word, parallelized by rayon
|
||||
|
|
|
@ -6,7 +6,7 @@ It will replace:
|
|||
- {{#PYO3_CRATE_VERSION}} with a relevant toml snippet (e.g. version = "0.13.2")
|
||||
|
||||
|
||||
Tested against mdbook 0.4.9.
|
||||
Tested against mdbook 0.4.10.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
|
|
@ -35,9 +35,18 @@ pyo3 = { {{#PYO3_CRATE_VERSION}} }
|
|||
|
||||
On Linux/macOS you might have to change `LD_LIBRARY_PATH` to include libpython, while on windows you might need to set `LIB` to include `pythonxy.lib` (where x and y are major and minor version), which is normally either in the `libs` or `Lib` folder of a Python installation.
|
||||
|
||||
## Distribution
|
||||
## Testing, building and distribution
|
||||
|
||||
There are two ways to distribute your module as a Python package: [setuptools-rust] and [maturin]. setuptools-rust needs several configuration files (`setup.py`, `MANIFEST.in`, `build-wheels.sh`, etc.). It allows (and sometimes requires) writing custom workflows in python. maturin has only few options and works without any additional configuration, instead it requires a rigid project structure and does not support some functionality of setuptools such as package data ([pyo3/maturin#258](https://github.com/PyO3/maturin/issues/258)), multiple extensions or running python scripts at build time.
|
||||
There are two main ways to test, build and distribute your module as a Python package: [setuptools-rust] and [maturin]. setuptools-rust needs several configuration files (`setup.py`, `MANIFEST.in`, `build-wheels.sh`, etc.). It allows (and sometimes requires) writing custom workflows in python. maturin has only few options and works without any additional configuration, instead it requires a rigid project structure and does not support some functionality of setuptools such as package data ([pyo3/maturin#258](https://github.com/PyO3/maturin/issues/258)), multiple extensions or running python scripts at build time.
|
||||
|
||||
### Manual builds
|
||||
|
||||
You can also symlink (or copy) and rename the shared library from the `target` folder:
|
||||
- on macOS, rename `libyour_module.dylib` to `your_module.so`.
|
||||
- on Windows, rename `libyour_module.dll` to `your_module.pyd`.
|
||||
- on Linux, rename `libyour_module.so` to `your_module.so`.
|
||||
|
||||
You can then open a Python shell in the same folder and you'll be able to run `import your_module`.
|
||||
|
||||
## `Py_LIMITED_API`/`abi3`
|
||||
|
||||
|
@ -77,7 +86,7 @@ As an advanced feature, you can build PyO3 wheel without calling Python interpre
|
|||
Due to limitations in the Python API, there are a few `pyo3` features that do
|
||||
not work when compiling for `abi3`. These are:
|
||||
|
||||
- `#[text_signature]` does not work on classes until Python 3.10 or greater.
|
||||
- `#[pyo3(text_signature = "...")]` does not work on classes until Python 3.10 or greater.
|
||||
- The `dict` and `weakref` options on classes are not supported until Python 3.9 or greater.
|
||||
- The buffer API is not supported.
|
||||
- Optimizations which rely on knowledge of the exact Python version compiled against.
|
||||
|
|
|
@ -169,6 +169,23 @@ impl MyClass {
|
|||
}
|
||||
```
|
||||
|
||||
Alternatively, if your `new` method may fail you can return `PyResult<Self>`.
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
struct MyClass {
|
||||
num: i32,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[new]
|
||||
fn new(num: i32) -> PyResult<Self> {
|
||||
Ok(MyClass { num })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If no method marked with `#[new]` is declared, object instances can only be
|
||||
created from Rust, but not from Python.
|
||||
|
||||
|
@ -349,7 +366,7 @@ struct MyClass {
|
|||
}
|
||||
```
|
||||
|
||||
The above would make the `num` property available for reading and writing from Python code as `self.num`.
|
||||
The above would make the `num` field available for reading and writing as a `self.num` Python property. To expose the property with a different name to the field, specify this alongside the rest of the options, e.g. `#[pyo3(get, set, name = "custom_name")]`.
|
||||
|
||||
Properties can be readonly or writeonly by using just `#[pyo3(get)]` or `#[pyo3(set)]` respectively.
|
||||
|
||||
|
@ -720,8 +737,6 @@ struct MyClass {
|
|||
debug: bool,
|
||||
}
|
||||
|
||||
impl pyo3::pyclass::PyClassAlloc for MyClass {}
|
||||
|
||||
unsafe impl pyo3::PyTypeInfo for MyClass {
|
||||
type AsRefTarget = PyCell<Self>;
|
||||
|
||||
|
@ -757,44 +772,51 @@ impl pyo3::class::impl_::PyClassImpl for MyClass {
|
|||
type BaseType = PyAny;
|
||||
type ThreadChecker = pyo3::class::impl_::ThreadCheckerStub<MyClass>;
|
||||
|
||||
fn for_each_method_def(visitor: &mut dyn FnMut(&pyo3::class::PyMethodDefType)) {
|
||||
fn for_each_method_def(visitor: &mut dyn FnMut(&[pyo3::class::PyMethodDefType])) {
|
||||
use pyo3::class::impl_::*;
|
||||
let collector = PyClassImplCollector::<MyClass>::new();
|
||||
collector.py_methods().iter()
|
||||
.chain(collector.py_class_descriptors())
|
||||
.chain(collector.object_protocol_methods())
|
||||
.chain(collector.async_protocol_methods())
|
||||
.chain(collector.context_protocol_methods())
|
||||
.chain(collector.descr_protocol_methods())
|
||||
.chain(collector.mapping_protocol_methods())
|
||||
.chain(collector.number_protocol_methods())
|
||||
.for_each(visitor)
|
||||
visitor(collector.py_methods());
|
||||
visitor(collector.py_class_descriptors());
|
||||
visitor(collector.object_protocol_methods());
|
||||
visitor(collector.async_protocol_methods());
|
||||
visitor(collector.context_protocol_methods());
|
||||
visitor(collector.descr_protocol_methods());
|
||||
visitor(collector.mapping_protocol_methods());
|
||||
visitor(collector.number_protocol_methods());
|
||||
}
|
||||
fn get_new() -> Option<pyo3::ffi::newfunc> {
|
||||
use pyo3::class::impl_::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.new_impl()
|
||||
}
|
||||
fn get_alloc() -> Option<pyo3::ffi::allocfunc> {
|
||||
use pyo3::class::impl_::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.alloc_impl()
|
||||
}
|
||||
fn get_free() -> Option<pyo3::ffi::freefunc> {
|
||||
use pyo3::class::impl_::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.free_impl()
|
||||
}
|
||||
fn get_call() -> Option<pyo3::ffi::PyCFunctionWithKeywords> {
|
||||
use pyo3::class::impl_::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.call_impl()
|
||||
}
|
||||
fn for_each_proto_slot(visitor: &mut dyn FnMut(&pyo3::ffi::PyType_Slot)) {
|
||||
fn for_each_proto_slot(visitor: &mut dyn FnMut(&[pyo3::ffi::PyType_Slot])) {
|
||||
// Implementation which uses dtolnay specialization to load all slots.
|
||||
use pyo3::class::impl_::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.object_protocol_slots()
|
||||
.iter()
|
||||
.chain(collector.number_protocol_slots())
|
||||
.chain(collector.iter_protocol_slots())
|
||||
.chain(collector.gc_protocol_slots())
|
||||
.chain(collector.descr_protocol_slots())
|
||||
.chain(collector.mapping_protocol_slots())
|
||||
.chain(collector.sequence_protocol_slots())
|
||||
.chain(collector.async_protocol_slots())
|
||||
.chain(collector.buffer_protocol_slots())
|
||||
.for_each(visitor);
|
||||
visitor(collector.object_protocol_slots());
|
||||
visitor(collector.number_protocol_slots());
|
||||
visitor(collector.iter_protocol_slots());
|
||||
visitor(collector.gc_protocol_slots());
|
||||
visitor(collector.descr_protocol_slots());
|
||||
visitor(collector.mapping_protocol_slots());
|
||||
visitor(collector.sequence_protocol_slots());
|
||||
visitor(collector.async_protocol_slots());
|
||||
visitor(collector.buffer_protocol_slots());
|
||||
}
|
||||
|
||||
fn get_buffer() -> Option<&'static pyo3::class::impl_::PyBufferProcs> {
|
||||
|
|
|
@ -20,7 +20,7 @@ The table below contains the Python type and the corresponding function argument
|
|||
| `float` | `f32`, `f64` | `&PyFloat` |
|
||||
| `complex` | `num_complex::Complex`[^1] | `&PyComplex` |
|
||||
| `list[T]` | `Vec<T>` | `&PyList` |
|
||||
| `dict[K, V]` | `HashMap<K, V>`, `BTreeMap<K, V>`, `hashbrown::HashMap<K, V>`[^2] | `&PyDict` |
|
||||
| `dict[K, V]` | `HashMap<K, V>`, `BTreeMap<K, V>`, `hashbrown::HashMap<K, V>`[^2], `indexmap::IndexMap<K, V>`[^3] | `&PyDict` |
|
||||
| `tuple[T, U]` | `(T, U)`, `Vec<T>` | `&PyTuple` |
|
||||
| `set[T]` | `HashSet<T>`, `BTreeSet<T>`, `hashbrown::HashSet<T>`[^2] | `&PySet` |
|
||||
| `frozenset[T]` | `HashSet<T>`, `BTreeSet<T>`, `hashbrown::HashSet<T>`[^2] | `&PyFrozenSet` |
|
||||
|
@ -94,3 +94,5 @@ Finally, the following Rust types are also able to convert to Python as return v
|
|||
[^1]: Requires the `num-complex` optional feature.
|
||||
|
||||
[^2]: Requires the `hashbrown` optional feature.
|
||||
|
||||
[^3]: Requires the `indexmap` optional feature.
|
||||
|
|
|
@ -21,7 +21,6 @@ It's also possible to tweak its configuration (mostly to tune its performance).
|
|||
```rust
|
||||
use log::info;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::wrap_pyfunction;
|
||||
|
||||
#[pyfunction]
|
||||
fn log_something() {
|
||||
|
|
|
@ -35,7 +35,7 @@ The Rust book suggests to [put integration tests inside a `tests/` directory](ht
|
|||
For a PyO3 `extension-module` project where the `crate-type` is set to `"cdylib"` in your `Cargo.toml`,
|
||||
the compiler won't be able to find your crate and will display errors such as `E0432` or `E0463`:
|
||||
|
||||
```
|
||||
```text
|
||||
error[E0432]: unresolved import `my_crate`
|
||||
--> tests/test_my_crate.rs:1:5
|
||||
|
|
||||
|
@ -45,7 +45,7 @@ error[E0432]: unresolved import `my_crate`
|
|||
|
||||
The best solution is to make your crate types include both `rlib` and `cdylib`:
|
||||
|
||||
```
|
||||
```toml
|
||||
# Cargo.toml
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
@ -56,3 +56,87 @@ crate-type = ["cdylib", "rlib"]
|
|||
This is because Ctrl-C raises a SIGINT signal, which is handled by the calling Python process by simply setting a flag to action upon later. This flag isn't checked while Rust code called from Python is executing, only once control returns to the Python interpreter.
|
||||
|
||||
You can give the Python interpreter a chance to process the signal properly by calling `Python::check_signals`. It's good practice to call this function regularly if you have a long-running Rust function so that your users can cancel it.
|
||||
|
||||
## `#[pyo3(get)]` clones my field!
|
||||
|
||||
You may have a nested struct similar to this:
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
#[derive(Clone)]
|
||||
struct Inner { /* fields omitted */ }
|
||||
|
||||
#[pyclass]
|
||||
struct Outer {
|
||||
#[pyo3(get)]
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl Outer {
|
||||
#[new]
|
||||
fn __new__() -> Self {
|
||||
Self { inner: Inner {} }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When Python code accesses `Outer`'s field, PyO3 will return a new object on every access (note that their addresses are different):
|
||||
|
||||
```python
|
||||
outer = Outer()
|
||||
|
||||
a = outer.inner
|
||||
b = outer.inner
|
||||
|
||||
assert a is b, f"a: {a}\nb: {b}"
|
||||
```
|
||||
|
||||
```text
|
||||
AssertionError: a: <builtins.Inner object at 0x00000238FFB9C7B0>
|
||||
b: <builtins.Inner object at 0x00000238FFB9C830>
|
||||
```
|
||||
|
||||
This can be especially confusing if the field is mutable, as getting the field and then mutating it won't persist - you'll just get a fresh clone of the original on the next access. Unfortunately Python and Rust don't agree about ownership - if PyO3 gave out references to (possibly) temporary Rust objects to Python code, Python code could then keep that reference alive indefinitely. Therefore returning Rust objects requires cloning.
|
||||
|
||||
If you don't want that cloning to happen, a workaround is to allocate the field on the Python heap and store a reference to that, by using [`Py<...>`]({{#PYO3_DOCS_URL}}/pyo3/struct.Py.html):
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
#[derive(Clone)]
|
||||
struct Inner { /* fields omitted */ }
|
||||
|
||||
#[pyclass]
|
||||
struct Outer {
|
||||
#[pyo3(get)]
|
||||
inner: Py<Inner>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl Outer {
|
||||
#[new]
|
||||
fn __new__(py: Python) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
inner: Py::new(py, Inner {})?,
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
This time `a` and `b` *are* the same object:
|
||||
```python
|
||||
outer = Outer()
|
||||
|
||||
a = outer.inner
|
||||
b = outer.inner
|
||||
|
||||
assert a is b, f"a: {a}\nb: {b}"
|
||||
print(f"a: {a}\nb: {b}")
|
||||
```
|
||||
|
||||
```text
|
||||
a: <builtins.Inner object at 0x0000020044FCC670>
|
||||
b: <builtins.Inner object at 0x0000020044FCC670>
|
||||
```
|
||||
The downside to this approach is that any Rust code working on the `Outer` struct now has to acquire the GIL to do anything with its field.
|
||||
|
||||
|
|
|
@ -69,6 +69,14 @@ The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use R
|
|||
- `FromPyObject` for `Vec` and `[T;N]` can perform a `memcpy` when the object supports the Python buffer protocol.
|
||||
- `ToBorrowedObject` can skip a reference count increase when the provided object is a Python native type.
|
||||
|
||||
### `num-bigint`
|
||||
|
||||
This feature adds a dependency on [num-bigint](https://docs.rs/num-bigint) and enables conversions into its [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html) and [`BigUint`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigUInt.html) types.
|
||||
|
||||
### `num-complex`
|
||||
|
||||
This feature 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.
|
||||
|
||||
### `serde`
|
||||
|
||||
The `serde` feature enables (de)serialization of Py<T> objects via [serde](https://serde.rs/).
|
||||
|
|
|
@ -6,7 +6,6 @@ One way is annotating a function with `#[pyfunction]` and then adding it to the
|
|||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::wrap_pyfunction;
|
||||
|
||||
#[pyfunction]
|
||||
fn double(x: usize) -> usize {
|
||||
|
@ -14,52 +13,47 @@ fn double(x: usize) -> usize {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(double, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
Alternatively there is a shorthand; the function can be placed inside the module definition and annotated with `#[pyfn]`, as below:
|
||||
Alternatively, there is a shorthand: the function can be placed inside the module definition and
|
||||
annotated with `#[pyfn]`, as below:
|
||||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pymodule]
|
||||
fn rust2py(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
|
||||
#[pyfn(m)]
|
||||
fn sum_as_string(_py: Python, a:i64, b:i64) -> PyResult<String> {
|
||||
Ok(format!("{}", a + b))
|
||||
fn double(x: usize) -> usize {
|
||||
x * 2
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
`#[pyfn(m)]` is just syntax sugar for `#[pyfunction]`, and takes all the same options documented in the rest of this chapter. The code above is expanded to the following:
|
||||
`#[pyfn(m)]` is just syntactic sugar for `#[pyfunction]`, and takes all the same options
|
||||
documented in the rest of this chapter. The code above is expanded to the following:
|
||||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pymodule]
|
||||
fn rust2py(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
|
||||
#[pyfunction]
|
||||
fn sum_as_string(_py: Python, a:i64, b:i64) -> PyResult<String> {
|
||||
Ok(format!("{}", a + b))
|
||||
fn double(x: usize) -> usize {
|
||||
x * 2
|
||||
}
|
||||
|
||||
m.add_function(pyo3::wrap_pyfunction!(sum_as_string, m)?)?;
|
||||
|
||||
m.add_function(wrap_pyfunction!(double, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
## Function options
|
||||
|
@ -72,7 +66,6 @@ The `#[pyo3]` attribute can be used to modify properties of the generated Python
|
|||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::wrap_pyfunction;
|
||||
|
||||
#[pyfunction]
|
||||
#[pyo3(name = "no_args")]
|
||||
|
@ -97,7 +90,6 @@ The `#[pyfunction]` attribute supports specifying details of argument parsing. T
|
|||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::wrap_pyfunction;
|
||||
use pyo3::types::PyDict;
|
||||
|
||||
#[pyfunction(kwds="**")]
|
||||
|
@ -117,7 +109,7 @@ fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
|
|||
## Making the function signature available to Python
|
||||
|
||||
In order to make the function signature available to Python to be retrieved via
|
||||
`inspect.signature`, use the `#[text_signature]` annotation as in the example
|
||||
`inspect.signature`, use the `#[pyo3(text_signature)]` annotation as in the example
|
||||
below. The `/` signifies the end of positional-only arguments. (This
|
||||
is not a feature of this library in particular, but the general format used by
|
||||
CPython for annotating signatures of built-in functions.)
|
||||
|
@ -127,7 +119,7 @@ use pyo3::prelude::*;
|
|||
|
||||
/// This function adds two unsigned 64-bit integers.
|
||||
#[pyfunction]
|
||||
#[text_signature = "(a, b, /)"]
|
||||
#[pyo3(text_signature = "(a, b, /)")]
|
||||
fn add(a: u64, b: u64) -> u64 {
|
||||
a + b
|
||||
}
|
||||
|
@ -142,7 +134,7 @@ use pyo3::types::PyType;
|
|||
// it works even if the item is not documented:
|
||||
|
||||
#[pyclass]
|
||||
#[text_signature = "(c, d, /)"]
|
||||
#[pyo3(text_signature = "(c, d, /)")]
|
||||
struct MyClass {}
|
||||
|
||||
#[pymethods]
|
||||
|
@ -154,17 +146,17 @@ impl MyClass {
|
|||
Self {}
|
||||
}
|
||||
// the self argument should be written $self
|
||||
#[text_signature = "($self, e, f)"]
|
||||
#[pyo3(text_signature = "($self, e, f)")]
|
||||
fn my_method(&self, e: i32, f: i32) -> i32 {
|
||||
e + f
|
||||
}
|
||||
#[classmethod]
|
||||
#[text_signature = "(cls, e, f)"]
|
||||
#[pyo3(text_signature = "(cls, e, f)")]
|
||||
fn my_class_method(cls: &PyType, e: i32, f: i32) -> i32 {
|
||||
e + f
|
||||
}
|
||||
#[staticmethod]
|
||||
#[text_signature = "(e, f)"]
|
||||
#[pyo3(text_signature = "(e, f)")]
|
||||
fn my_static_method(e: i32, f: i32) -> i32 {
|
||||
e + f
|
||||
}
|
||||
|
@ -180,7 +172,7 @@ Alternatively, simply make sure the first line of your docstring is
|
|||
formatted like in the following example. Please note that the newline after the
|
||||
`--` is mandatory. The `/` signifies the end of positional-only arguments.
|
||||
|
||||
`#[text_signature]` should be preferred, since it will override automatically
|
||||
`#[pyo3(text_signature)]` should be preferred, since it will override automatically
|
||||
generated signatures when those are added in a future version of PyO3.
|
||||
|
||||
```rust
|
||||
|
@ -255,13 +247,13 @@ in Python code.
|
|||
|
||||
### Accessing the module of a function
|
||||
|
||||
It is possible to access the module of a `#[pyfunction]` in the function body by passing the `pass_module` argument to the attribute:
|
||||
It is possible to access the module of a `#[pyfunction]` in the function body by using `#[pyo3(pass_module)]` option:
|
||||
|
||||
```rust
|
||||
use pyo3::wrap_pyfunction;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyfunction(pass_module)]
|
||||
#[pyfunction]
|
||||
#[pyo3(pass_module)]
|
||||
fn pyfunction_with_module(module: &PyModule) -> PyResult<&str> {
|
||||
module.name()
|
||||
}
|
||||
|
@ -279,12 +271,11 @@ in the function body.
|
|||
|
||||
## Accessing the FFI functions
|
||||
|
||||
In order to make Rust functions callable from Python, PyO3 generates a
|
||||
`extern "C" Fn(slf: *mut PyObject, args: *mut PyObject, kwargs: *mut PyObject) -> *mut Pyobject`
|
||||
function and embeds the call to the Rust function inside this FFI-wrapper function. This
|
||||
wrapper handles extraction of the regular arguments and the keyword arguments from the input
|
||||
`PyObjects`. Since this function is not user-defined but required to build a `PyCFunction`, PyO3
|
||||
offers the `raw_pycfunction!()` macro to get the identifier of this generated wrapper.
|
||||
In order to make Rust functions callable from Python, PyO3 generates an `extern "C"`
|
||||
function whose exact signature depends on the Rust signature. (PyO3 chooses the optimal
|
||||
Python argument passing convention.) It then embeds the call to the Rust function inside this
|
||||
FFI-wrapper function. This wrapper handles extraction of the regular arguments and the keyword
|
||||
arguments from the input `PyObject`s.
|
||||
|
||||
The `wrap_pyfunction` macro can be used to directly get a `PyCFunction` given a
|
||||
`#[pyfunction]` and a `PyModule`: `wrap_pyfunction!(rust_fun, module)`.
|
||||
|
|
|
@ -1,99 +1,183 @@
|
|||
# Python Modules
|
||||
|
||||
You can create a module as follows:
|
||||
You can create a module using `#[pymodule]`:
|
||||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
// add bindings to the generated Python module
|
||||
// N.B: "rust2py" must be the name of the `.so` or `.pyd` file.
|
||||
#[pyfunction]
|
||||
fn double(x: usize) -> usize {
|
||||
x * 2
|
||||
}
|
||||
|
||||
/// This module is implemented in Rust.
|
||||
#[pymodule]
|
||||
fn rust2py(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
// PyO3 aware function. All of our Python interfaces could be declared in a separate module.
|
||||
// Note that the `#[pyfn()]` annotation automatically converts the arguments from
|
||||
// Python objects to Rust values, and the Rust return value back into a Python object.
|
||||
// The `_py` argument represents that we're holding the GIL.
|
||||
#[pyfn(m)]
|
||||
#[pyo3(name = "sum_as_string")]
|
||||
fn sum_as_string_py(_py: Python, a: i64, b: i64) -> PyResult<String> {
|
||||
let out = sum_as_string(a, b);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(double, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// logic implemented as a normal Rust function
|
||||
fn sum_as_string(a: i64, b: i64) -> String {
|
||||
format!("{}", a + b)
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
The `#[pymodule]` procedural macro attribute takes care of exporting the initialization function of your
|
||||
module to Python. It can take as an argument the name of your module, which must be the name of the `.so`
|
||||
or `.pyd` file; the default is the Rust function's name.
|
||||
The `#[pymodule]` procedural macro takes care of exporting the initialization function of your
|
||||
module to Python.
|
||||
|
||||
If the name of the module (the default being the function name) does not match the name of the `.so` or
|
||||
`.pyd` file, you will get an import error in Python with the following message:
|
||||
The module's name defaults to the name of the Rust function. You can override the module name by
|
||||
using `#[pyo3(name = "custom_name")]`:
|
||||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyfunction]
|
||||
fn double(x: usize) -> usize {
|
||||
x * 2
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
#[pyo3(name = "custom_name")]
|
||||
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(double, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
The name of the module must match the name of the `.so` or `.pyd`
|
||||
file. Otherwise, you will get an import error in Python with the following message:
|
||||
`ImportError: dynamic module does not define module export function (PyInit_name_of_your_module)`
|
||||
|
||||
To import the module, either copy the shared library as described in [the README](https://github.com/PyO3/pyo3)
|
||||
or use a tool, e.g. `maturin develop` with [maturin](https://github.com/PyO3/maturin) or
|
||||
To import the module, either:
|
||||
- copy the shared library as described in [Manual builds](building_and_distribution.html#manual-builds), or
|
||||
- use a tool, e.g. `maturin develop` with [maturin](https://github.com/PyO3/maturin) or
|
||||
`python setup.py develop` with [setuptools-rust](https://github.com/PyO3/setuptools-rust).
|
||||
|
||||
## Documentation
|
||||
|
||||
The [Rust doc comments](https://doc.rust-lang.org/stable/book/first-edition/comments.html) of the module
|
||||
The [Rust doc comments](https://doc.rust-lang.org/stable/book/ch03-04-comments.html) of the module
|
||||
initialization function will be applied automatically as the Python docstring of your module.
|
||||
|
||||
```python
|
||||
import rust2py
|
||||
For example, building off of the above code, this will print `This module is implemented in Rust.`:
|
||||
|
||||
print(rust2py.__doc__)
|
||||
```python
|
||||
import my_extension
|
||||
|
||||
print(my_extension.__doc__)
|
||||
```
|
||||
|
||||
Which means that the above Python code will print `This module is implemented in Rust.`.
|
||||
## Organizing your module registration code
|
||||
|
||||
## Modules as objects
|
||||
For most projects, it's adequate to centralize all your FFI code into a single Rust module.
|
||||
|
||||
In Python, modules are first class objects. This means that you can store them as values or add them to
|
||||
dicts or other modules:
|
||||
However, for larger projects, it can be helpful to split your Rust code into several Rust modules to keep your code
|
||||
readable. Unfortunately, though, some of the macros like `wrap_pyfunction!` do not yet work when used on code defined
|
||||
in other modules ([#1709](https://github.com/PyO3/pyo3/issues/1709)). One way to work around this is to pass
|
||||
references to the `PyModule` so that each module registers its own FFI code. For example:
|
||||
|
||||
```rust
|
||||
// src/lib.rs
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pymodule]
|
||||
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
dirutil::register(py, m)?;
|
||||
osutil::register(py, m)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// src/dirutil.rs
|
||||
# mod dirutil {
|
||||
use pyo3::prelude::*;
|
||||
|
||||
pub(crate) fn register(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_class::<SomeClass>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
struct SomeClass {
|
||||
x: usize,
|
||||
}
|
||||
# }
|
||||
|
||||
// src/osutil.rs
|
||||
# mod osutil {
|
||||
use pyo3::prelude::*;
|
||||
|
||||
pub(crate) fn register(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(determine_current_os, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn determine_current_os() -> String {
|
||||
"linux".to_owned()
|
||||
}
|
||||
# }
|
||||
```
|
||||
|
||||
Another workaround for splitting FFI code across multiple modules ([#1709](https://github.com/PyO3/pyo3/issues/1709))
|
||||
is to add `use module::*`, like this:
|
||||
|
||||
```rust
|
||||
// src/lib.rs
|
||||
use pyo3::prelude::*;
|
||||
use osutil::*;
|
||||
|
||||
#[pymodule]
|
||||
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(determine_current_os, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// src/osutil.rs
|
||||
# mod osutil {
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyfunction]
|
||||
pub(crate) fn determine_current_os() -> String {
|
||||
"linux".to_owned()
|
||||
}
|
||||
# }
|
||||
```
|
||||
|
||||
## Python submodules
|
||||
|
||||
You can create a module hierarchy within a single extension module by using
|
||||
[`PyModule.add_submodule()`]({{#PYO3_DOCS_URL}}/pyo3/prelude/struct.PyModule.html#method.add_submodule).
|
||||
For example, you could define the modules `parent_module` and `parent_module.child_module`.
|
||||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::{wrap_pyfunction, wrap_pymodule};
|
||||
use pyo3::types::IntoPyDict;
|
||||
|
||||
#[pyfunction]
|
||||
fn subfunction() -> String {
|
||||
"Subfunction".to_string()
|
||||
}
|
||||
|
||||
fn init_submodule(module: &PyModule) -> PyResult<()> {
|
||||
module.add_function(wrap_pyfunction!(subfunction, module)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
fn supermodule(py: Python, module: &PyModule) -> PyResult<()> {
|
||||
let submod = PyModule::new(py, "submodule")?;
|
||||
init_submodule(submod)?;
|
||||
module.add_submodule(submod)?;
|
||||
fn parent_module(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
register_child_module(py, m)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_child_module(py: Python, parent_module: &PyModule) -> PyResult<()> {
|
||||
let child_module = PyModule::new(py, "child_module")?;
|
||||
child_module.add_function(wrap_pyfunction!(func, child_module)?)?;
|
||||
parent_module.add_submodule(child_module)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn func() -> String {
|
||||
"func".to_string()
|
||||
}
|
||||
|
||||
# Python::with_gil(|py| {
|
||||
# let supermodule = wrap_pymodule!(supermodule)(py);
|
||||
# let ctx = [("supermodule", supermodule)].into_py_dict(py);
|
||||
# use pyo3::wrap_pymodule;
|
||||
# use pyo3::types::IntoPyDict;
|
||||
# let parent_module = wrap_pymodule!(parent_module)(py);
|
||||
# let ctx = [("parent_module", parent_module)].into_py_dict(py);
|
||||
#
|
||||
# py.run("assert supermodule.submodule.subfunction() == 'Subfunction'", None, Some(&ctx)).unwrap();
|
||||
# py.run("assert parent_module.child_module.func() == 'func'", None, Some(&ctx)).unwrap();
|
||||
# })
|
||||
```
|
||||
|
||||
This way, you can create a module hierarchy within a single extension module.
|
||||
Note that this does not define a package, so this won’t allow Python code to directly import
|
||||
submodules by using `from parent_module import child_module`. For more information, see
|
||||
[#759](https://github.com/PyO3/pyo3/issues/759) and
|
||||
[#1517](https://github.com/PyO3/pyo3/issues/1517#issuecomment-808664021).
|
||||
|
||||
It is not necessary to add `#[pymodule]` on nested modules, this is only required on the top-level module.
|
||||
It is not necessary to add `#[pymodule]` on nested modules, which is only required on the top-level module.
|
||||
|
|
|
@ -461,7 +461,6 @@ It is also required to make the struct public.
|
|||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::wrap_pyfunction;
|
||||
use pyo3::types::PyAny;
|
||||
|
||||
pub trait Model {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.14.0-alpha.0"
|
||||
version = "0.14.1"
|
||||
description = "Build configuration for the PyO3 ecosystem"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||
|
@ -11,6 +11,7 @@ license = "Apache-2.0"
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
once_cell = "1"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
#[allow(dead_code)]
|
||||
#[path = "src/impl_.rs"]
|
||||
mod impl_;
|
||||
|
||||
fn main() {
|
||||
// Print out error messages using display, to get nicer formatting.
|
||||
if let Err(e) = impl_::configure() {
|
||||
eprintln!("error: {}", e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
// Empty build script to force cargo to produce the "OUT_DIR" environment variable.
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
use pyo3_build_config::{find_interpreter, get_config_from_interpreter};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = get_config_from_interpreter(&find_interpreter()?)?;
|
||||
|
||||
println!("implementation: {}", config.implementation);
|
||||
println!("interpreter version: {}", config.version);
|
||||
println!("interpreter path: {:?}", config.executable);
|
||||
println!("libdir: {:?}", config.libdir);
|
||||
println!("shared: {}", config.shared);
|
||||
println!("base prefix: {:?}", config.base_prefix);
|
||||
println!("ld_version: {:?}", config.ld_version);
|
||||
println!("pointer width: {:?}", config.calcsize_pointer);
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/// A simple macro for returning an error. Resembles anyhow::bail.
|
||||
#[macro_export]
|
||||
macro_rules! bail {
|
||||
($msg: expr) => { return Err($msg.into()); };
|
||||
($fmt: literal $($args: tt)+) => { return Err(format!($fmt $($args)+).into()); };
|
||||
}
|
||||
|
||||
/// A simple macro for checking a condition. Resembles anyhow::ensure.
|
||||
#[macro_export]
|
||||
macro_rules! ensure {
|
||||
($condition:expr, $($args: tt)+) => { if !($condition) { bail!($($args)+) } };
|
||||
}
|
||||
|
||||
/// Show warning. If needed, please extend this macro to support arguments.
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($msg: literal) => {
|
||||
println!(concat!("cargo:warning=", $msg));
|
||||
};
|
||||
}
|
||||
|
||||
/// A simple error implementation which allows chaining of errors, inspired somewhat by anyhow.
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
value: String,
|
||||
source: Option<Box<dyn std::error::Error>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
self.source.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(value: String) -> Self {
|
||||
Self {
|
||||
value,
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'_ str> for Error {
|
||||
fn from(value: &str) -> Self {
|
||||
value.to_string().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::convert::Infallible> for Error {
|
||||
fn from(_: std::convert::Infallible) -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
pub trait Context<T> {
|
||||
fn context(self, message: impl Into<String>) -> Result<T>;
|
||||
fn with_context(self, message: impl FnOnce() -> String) -> Result<T>;
|
||||
}
|
||||
|
||||
impl<T, E> Context<T> for Result<T, E>
|
||||
where
|
||||
E: std::error::Error + 'static,
|
||||
{
|
||||
fn context(self, message: impl Into<String>) -> Result<T> {
|
||||
self.map_err(|error| Error {
|
||||
value: message.into(),
|
||||
source: Some(Box::new(error)),
|
||||
})
|
||||
}
|
||||
|
||||
fn with_context(self, message: impl FnOnce() -> String) -> Result<T> {
|
||||
self.map_err(|error| Error {
|
||||
value: message(),
|
||||
source: Some(Box::new(error)),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -4,38 +4,24 @@ use std::{
|
|||
env,
|
||||
ffi::OsString,
|
||||
fmt::Display,
|
||||
fs::{self, DirEntry, File},
|
||||
io::Write,
|
||||
fs::{self, DirEntry},
|
||||
io::{BufRead, BufReader, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
bail, ensure,
|
||||
errors::{Context, Error, Result},
|
||||
warn,
|
||||
};
|
||||
|
||||
/// Minimum Python version PyO3 supports.
|
||||
const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 6 };
|
||||
/// Maximum Python version that can be used as minimum required Python version with abi3.
|
||||
const ABI3_MAX_MINOR: u8 = 9;
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
// A simple macro for returning an error. Resembles anyhow::bail.
|
||||
macro_rules! bail {
|
||||
($msg: expr) => { return Err($msg.into()); };
|
||||
($fmt: literal $($args: tt)+) => { return Err(format!($fmt $($args)+).into()); };
|
||||
}
|
||||
|
||||
// A simple macro for checking a condition. Resembles anyhow::ensure.
|
||||
macro_rules! ensure {
|
||||
($condition:expr, $($args: tt)+) => { if !($condition) { bail!($($args)+) } };
|
||||
}
|
||||
|
||||
// Show warning. If needed, please extend this macro to support arguments.
|
||||
macro_rules! warn {
|
||||
($msg: literal) => {
|
||||
println!(concat!("cargo:warning=", $msg));
|
||||
};
|
||||
}
|
||||
|
||||
/// Gets an environment variable owned by cargo.
|
||||
///
|
||||
/// Environment variables set by cargo are expected to be valid UTF8.
|
||||
|
@ -55,7 +41,7 @@ fn env_var(var: &str) -> Option<OsString> {
|
|||
/// Usually this is queried directly from the Python interpreter. When the `PYO3_NO_PYTHON` variable
|
||||
/// is set, or during cross compile situations, then alternative strategies are used to populate
|
||||
/// this type.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
pub struct InterpreterConfig {
|
||||
pub version: PythonVersion,
|
||||
pub libdir: Option<String>,
|
||||
|
@ -99,6 +85,114 @@ impl InterpreterConfig {
|
|||
pub fn is_pypy(&self) -> bool {
|
||||
self.implementation == PythonImplementation::PyPy
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn from_reader(reader: impl Read) -> Result<Self> {
|
||||
let reader = BufReader::new(reader);
|
||||
let mut lines = reader.lines();
|
||||
|
||||
macro_rules! parse_line {
|
||||
($value:literal) => {
|
||||
lines
|
||||
.next()
|
||||
.ok_or(concat!("reached end of config when reading ", $value))?
|
||||
.context(concat!("failed to read ", $value, " from config"))?
|
||||
.parse()
|
||||
.context(concat!("failed to parse ", $value, " from config"))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! parse_option_line {
|
||||
($value:literal) => {
|
||||
parse_option_string(
|
||||
lines
|
||||
.next()
|
||||
.ok_or(concat!("reached end of config when reading ", $value))?
|
||||
.context(concat!("failed to read ", $value, " from config"))?,
|
||||
)
|
||||
.context(concat!("failed to parse ", $value, "from config"))
|
||||
};
|
||||
}
|
||||
|
||||
let major = parse_line!("major version")?;
|
||||
let minor = parse_line!("minor version")?;
|
||||
let libdir = parse_option_line!("libdir")?;
|
||||
let shared = parse_line!("shared")?;
|
||||
let abi3 = parse_line!("abi3")?;
|
||||
let ld_version = parse_option_line!("ld_version")?;
|
||||
let base_prefix = parse_option_line!("base_prefix")?;
|
||||
let executable = parse_option_line!("executable")?;
|
||||
let calcsize_pointer = parse_option_line!("calcsize_pointer")?;
|
||||
let implementation = parse_line!("implementation")?;
|
||||
let mut build_flags = BuildFlags(HashSet::new());
|
||||
for line in lines {
|
||||
build_flags
|
||||
.0
|
||||
.insert(line.context("failed to read flag from config")?.parse()?);
|
||||
}
|
||||
Ok(InterpreterConfig {
|
||||
version: PythonVersion { major, minor },
|
||||
libdir,
|
||||
shared,
|
||||
abi3,
|
||||
ld_version,
|
||||
base_prefix,
|
||||
executable,
|
||||
calcsize_pointer,
|
||||
implementation,
|
||||
build_flags,
|
||||
})
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn to_writer(&self, mut writer: impl Write) -> Result<()> {
|
||||
macro_rules! write_line {
|
||||
($value:expr) => {
|
||||
writeln!(writer, "{}", $value).context(concat!(
|
||||
"failed to write ",
|
||||
stringify!($value),
|
||||
" to config"
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! write_option_line {
|
||||
($opt:expr) => {
|
||||
match &$opt {
|
||||
Some(value) => writeln!(writer, "{}", value),
|
||||
None => writeln!(writer, "null"),
|
||||
}
|
||||
.context(concat!(
|
||||
"failed to write ",
|
||||
stringify!($value),
|
||||
" to config"
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
write_line!(self.version.major)?;
|
||||
write_line!(self.version.minor)?;
|
||||
write_option_line!(self.libdir)?;
|
||||
write_line!(self.shared)?;
|
||||
write_line!(self.abi3)?;
|
||||
write_option_line!(self.ld_version)?;
|
||||
write_option_line!(self.base_prefix)?;
|
||||
write_option_line!(self.executable)?;
|
||||
write_option_line!(self.calcsize_pointer)?;
|
||||
write_line!(self.implementation)?;
|
||||
for flag in &self.build_flags.0 {
|
||||
write_line!(flag)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_option_string<T: FromStr>(string: String) -> Result<Option<T>, <T as FromStr>::Err> {
|
||||
if string == "null" {
|
||||
Ok(None)
|
||||
} else {
|
||||
string.parse().map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
|
@ -123,13 +217,22 @@ pub enum PythonImplementation {
|
|||
PyPy,
|
||||
}
|
||||
|
||||
impl Display for PythonImplementation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
PythonImplementation::CPython => write!(f, "CPython"),
|
||||
PythonImplementation::PyPy => write!(f, "PyPy"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PythonImplementation {
|
||||
type Err = Box<dyn std::error::Error>;
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
"CPython" => Ok(PythonImplementation::CPython),
|
||||
"PyPy" => Ok(PythonImplementation::PyPy),
|
||||
_ => bail!("Invalid interpreter: {}", s),
|
||||
_ => bail!("unknown interpreter: {}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,7 +243,9 @@ fn is_abi3() -> bool {
|
|||
|
||||
trait GetPrimitive {
|
||||
fn get_bool(&self, key: &str) -> Result<bool>;
|
||||
fn get_numeric<T: FromStr>(&self, key: &str) -> Result<T>;
|
||||
fn get_numeric<T: FromStr>(&self, key: &str) -> Result<T>
|
||||
where
|
||||
T::Err: std::error::Error + 'static;
|
||||
}
|
||||
|
||||
impl GetPrimitive for HashMap<String, String> {
|
||||
|
@ -156,11 +261,14 @@ impl GetPrimitive for HashMap<String, String> {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_numeric<T: FromStr>(&self, key: &str) -> Result<T> {
|
||||
fn get_numeric<T: FromStr>(&self, key: &str) -> Result<T>
|
||||
where
|
||||
T::Err: std::error::Error + 'static,
|
||||
{
|
||||
self.get(key)
|
||||
.ok_or(format!("{} is not defined", key))?
|
||||
.parse::<T>()
|
||||
.map_err(|_| format!("Could not parse value of {}", key).into())
|
||||
.with_context(|| format!("Could not parse value of {}", key))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,34 +342,75 @@ fn cross_compiling() -> Result<Option<CrossCompileConfig>> {
|
|||
}))
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub enum BuildFlag {
|
||||
WITH_THREAD,
|
||||
Py_DEBUG,
|
||||
Py_REF_DEBUG,
|
||||
Py_TRACE_REFS,
|
||||
COUNT_ALLOCS,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl Display for BuildFlag {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
BuildFlag::Other(flag) => write!(f, "{}", flag),
|
||||
_ => write!(f, "{:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for BuildFlag {
|
||||
type Err = std::convert::Infallible;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"WITH_THREAD" => Ok(BuildFlag::WITH_THREAD),
|
||||
"Py_DEBUG" => Ok(BuildFlag::Py_DEBUG),
|
||||
"Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG),
|
||||
"Py_TRACE_REFS" => Ok(BuildFlag::Py_TRACE_REFS),
|
||||
"COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS),
|
||||
other => Ok(BuildFlag::Other(other.to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of python interpreter compile-time preprocessor defines that
|
||||
/// we will pick up and pass to rustc via `--cfg=py_sys_config={varname}`;
|
||||
/// this allows using them conditional cfg attributes in the .rs files, so
|
||||
///
|
||||
/// #[cfg(py_sys_config="{varname}"]
|
||||
/// ```rust
|
||||
/// #[cfg(py_sys_config="{varname}")]
|
||||
/// # struct Foo;
|
||||
/// ```
|
||||
///
|
||||
/// is the equivalent of `#ifdef {varname}` in C.
|
||||
///
|
||||
/// see Misc/SpecialBuilds.txt in the python source for what these mean.
|
||||
#[derive(Debug)]
|
||||
pub struct BuildFlags(pub HashSet<&'static str>);
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
pub struct BuildFlags(pub HashSet<BuildFlag>);
|
||||
|
||||
impl BuildFlags {
|
||||
const ALL: [&'static str; 5] = [
|
||||
const ALL: [BuildFlag; 5] = [
|
||||
// TODO: Remove WITH_THREAD once Python 3.6 support dropped (as it's always on).
|
||||
"WITH_THREAD",
|
||||
"Py_DEBUG",
|
||||
"Py_REF_DEBUG",
|
||||
"Py_TRACE_REFS",
|
||||
"COUNT_ALLOCS",
|
||||
BuildFlag::WITH_THREAD,
|
||||
BuildFlag::Py_DEBUG,
|
||||
BuildFlag::Py_REF_DEBUG,
|
||||
BuildFlag::Py_TRACE_REFS,
|
||||
BuildFlag::COUNT_ALLOCS,
|
||||
];
|
||||
|
||||
fn from_config_map(config_map: &HashMap<String, String>) -> Self {
|
||||
Self(
|
||||
BuildFlags::ALL
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|flag| config_map.get(*flag).map_or(false, |value| value == "1"))
|
||||
.cloned()
|
||||
.filter(|flag| {
|
||||
config_map
|
||||
.get(&flag.to_string())
|
||||
.map_or(false, |value| value == "1")
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
@ -270,7 +419,8 @@ impl BuildFlags {
|
|||
/// the interpreter and printing variables of interest from
|
||||
/// sysconfig.get_config_vars.
|
||||
fn from_interpreter(interpreter: &Path) -> Result<Self> {
|
||||
if cargo_env_var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
|
||||
// If we're on a Windows host, then Python won't have any useful config vars
|
||||
if cfg!(windows) {
|
||||
return Ok(Self::windows_hardcoded());
|
||||
}
|
||||
|
||||
|
@ -281,7 +431,7 @@ impl BuildFlags {
|
|||
script.push_str(&format!("print(config.get('{}', '0'))\n", k));
|
||||
}
|
||||
|
||||
let stdout = run_python_script(&interpreter, &script)?;
|
||||
let stdout = run_python_script(interpreter, &script)?;
|
||||
let split_stdout: Vec<&str> = stdout.trim_end().lines().collect();
|
||||
ensure!(
|
||||
split_stdout.len() == BuildFlags::ALL.len(),
|
||||
|
@ -292,7 +442,7 @@ impl BuildFlags {
|
|||
.iter()
|
||||
.zip(split_stdout)
|
||||
.filter(|(_, flag_value)| *flag_value == "1")
|
||||
.map(|(&flag, _)| flag)
|
||||
.map(|(flag, _)| flag.clone())
|
||||
.collect();
|
||||
|
||||
Ok(Self(flags))
|
||||
|
@ -302,36 +452,36 @@ impl BuildFlags {
|
|||
// sysconfig is missing all the flags on windows, so we can't actually
|
||||
// query the interpreter directly for its build flags.
|
||||
let mut flags = HashSet::new();
|
||||
flags.insert("WITH_THREAD");
|
||||
flags.insert(BuildFlag::WITH_THREAD);
|
||||
|
||||
// Uncomment these manually if your python was built with these and you want
|
||||
// the cfg flags to be set in rust.
|
||||
//
|
||||
// map.insert("Py_DEBUG", "1");
|
||||
// map.insert("Py_REF_DEBUG", "1");
|
||||
// map.insert("Py_TRACE_REFS", "1");
|
||||
// map.insert("COUNT_ALLOCS", 1");
|
||||
// flags.insert(BuildFlag::Py_DEBUG);
|
||||
// flags.insert(BuildFlag::Py_REF_DEBUG);
|
||||
// flags.insert(BuildFlag::Py_TRACE_REFS);
|
||||
// flags.insert(BuildFlag::COUNT_ALLOCS;
|
||||
Self(flags)
|
||||
}
|
||||
|
||||
fn abi3() -> Self {
|
||||
let mut flags = HashSet::new();
|
||||
flags.insert("WITH_THREAD");
|
||||
flags.insert(BuildFlag::WITH_THREAD);
|
||||
Self(flags)
|
||||
}
|
||||
|
||||
fn fixup(mut self, version: PythonVersion, implementation: PythonImplementation) -> Self {
|
||||
if self.0.contains("Py_DEBUG") {
|
||||
self.0.insert("Py_REF_DEBUG");
|
||||
if self.0.contains(&BuildFlag::Py_DEBUG) {
|
||||
self.0.insert(BuildFlag::Py_REF_DEBUG);
|
||||
if version <= PythonVersion::PY37 {
|
||||
// Py_DEBUG only implies Py_TRACE_REFS until Python 3.7
|
||||
self.0.insert("Py_TRACE_REFS");
|
||||
self.0.insert(BuildFlag::Py_TRACE_REFS);
|
||||
}
|
||||
}
|
||||
|
||||
// WITH_THREAD is always on for Python 3.7, and for PyPy.
|
||||
if implementation == PythonImplementation::PyPy || version >= PythonVersion::PY37 {
|
||||
self.0.insert("WITH_THREAD");
|
||||
self.0.insert(BuildFlag::WITH_THREAD);
|
||||
}
|
||||
|
||||
self
|
||||
|
@ -354,10 +504,16 @@ fn parse_script_output(output: &str) -> HashMap<String, String> {
|
|||
/// python executable and library. Here it is read and added to a script to extract only what is
|
||||
/// necessary. This necessitates a python interpreter for the host machine to work.
|
||||
fn parse_sysconfigdata(config_path: impl AsRef<Path>) -> Result<HashMap<String, String>> {
|
||||
let mut script = fs::read_to_string(config_path)?;
|
||||
let mut script = fs::read_to_string(config_path.as_ref()).with_context(|| {
|
||||
format!(
|
||||
"failed to read config from {}",
|
||||
config_path.as_ref().display()
|
||||
)
|
||||
})?;
|
||||
script += r#"
|
||||
print("version_major", build_time_vars["VERSION"][0]) # 3
|
||||
print("version_minor", build_time_vars["VERSION"][2]) # E.g., 8
|
||||
print("SOABI", build_time_vars.get("SOABI", ""))
|
||||
KEYS = [
|
||||
"WITH_THREAD",
|
||||
"Py_DEBUG",
|
||||
|
@ -417,7 +573,7 @@ fn ends_with(entry: &DirEntry, pat: &str) -> bool {
|
|||
///
|
||||
/// [1]: https://github.com/python/cpython/blob/3.5/Lib/sysconfig.py#L389
|
||||
fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result<PathBuf> {
|
||||
let sysconfig_paths = search_lib_dir(&cross.lib_dir, &cross);
|
||||
let sysconfig_paths = search_lib_dir(&cross.lib_dir, cross);
|
||||
let sysconfig_name = env_var("_PYTHON_SYSCONFIGDATA_NAME");
|
||||
let mut sysconfig_paths = sysconfig_paths
|
||||
.iter()
|
||||
|
@ -484,6 +640,24 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
|
|||
};
|
||||
sysconfig_paths.extend(sysc);
|
||||
}
|
||||
// If we got more than one file, only take those that contain the arch name.
|
||||
// For ubuntu 20.04 with host architecture x86_64 and a foreign architecture of armhf
|
||||
// this reduces the number of candidates to 1:
|
||||
//
|
||||
// $ find /usr/lib/python3.8/ -name '_sysconfigdata*.py' -not -lname '*'
|
||||
// /usr/lib/python3.8/_sysconfigdata__x86_64-linux-gnu.py
|
||||
// /usr/lib/python3.8/_sysconfigdata__arm-linux-gnueabihf.py
|
||||
if sysconfig_paths.len() > 1 {
|
||||
let temp = sysconfig_paths
|
||||
.iter()
|
||||
.filter(|p| p.to_string_lossy().contains(&cross.arch))
|
||||
.cloned()
|
||||
.collect::<Vec<PathBuf>>();
|
||||
if !temp.is_empty() {
|
||||
sysconfig_paths = temp;
|
||||
}
|
||||
}
|
||||
|
||||
sysconfig_paths
|
||||
}
|
||||
|
||||
|
@ -506,9 +680,19 @@ fn load_cross_compile_from_sysconfigdata(
|
|||
None => format!("{}.{}", major, minor),
|
||||
};
|
||||
let calcsize_pointer = sysconfig_data.get_numeric("SIZEOF_VOID_P").ok();
|
||||
let soabi = match sysconfig_data.get("SOABI") {
|
||||
Some(s) => s,
|
||||
None => bail!("sysconfigdata did not define SOABI"),
|
||||
};
|
||||
|
||||
let version = PythonVersion { major, minor };
|
||||
let implementation = PythonImplementation::CPython;
|
||||
let implementation = if soabi.starts_with("pypy") {
|
||||
PythonImplementation::PyPy
|
||||
} else if soabi.starts_with("cpython") {
|
||||
PythonImplementation::CPython
|
||||
} else {
|
||||
bail!("unsupported Python interpreter");
|
||||
};
|
||||
|
||||
Ok(InterpreterConfig {
|
||||
version,
|
||||
|
@ -602,7 +786,8 @@ fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
|
|||
err
|
||||
),
|
||||
Ok(ok) if !ok.status.success() => bail!("Python script failed"),
|
||||
Ok(ok) => Ok(String::from_utf8(ok.stdout)?),
|
||||
Ok(ok) => Ok(String::from_utf8(ok.stdout)
|
||||
.context("failed to parse Python script output as utf-8")?),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -626,13 +811,16 @@ fn get_venv_path() -> Option<PathBuf> {
|
|||
/// 2. If in a virtualenv, that environment's interpreter is used.
|
||||
/// 3. `python`, if this is functional a Python 3.x interpreter
|
||||
/// 4. `python3`, as above
|
||||
fn find_interpreter() -> Result<PathBuf> {
|
||||
pub fn find_interpreter() -> Result<PathBuf> {
|
||||
if let Some(exe) = env_var("PYO3_PYTHON") {
|
||||
Ok(exe.into())
|
||||
} else if let Some(venv_path) = get_venv_path() {
|
||||
match cargo_env_var("CARGO_CFG_TARGET_OS").unwrap().as_str() {
|
||||
"windows" => Ok(venv_path.join("Scripts\\python")),
|
||||
_ => Ok(venv_path.join("bin/python")),
|
||||
// Use cfg rather can CARGO_TARGET_OS because this affects how files are located on the
|
||||
// host OS
|
||||
if cfg!(windows) {
|
||||
Ok(venv_path.join("Scripts\\python"))
|
||||
} else {
|
||||
Ok(venv_path.join("bin/python"))
|
||||
}
|
||||
} else {
|
||||
println!("cargo:rerun-if-env-changed=PATH");
|
||||
|
@ -652,7 +840,7 @@ fn find_interpreter() -> Result<PathBuf> {
|
|||
}
|
||||
|
||||
/// Extract compilation vars from the specified interpreter.
|
||||
fn get_config_from_interpreter(interpreter: &Path) -> Result<InterpreterConfig> {
|
||||
pub fn get_config_from_interpreter(interpreter: &Path) -> Result<InterpreterConfig> {
|
||||
let script = r#"
|
||||
# Allow the script to run on Python 2, so that nicer error can be printed later.
|
||||
from __future__ import print_function
|
||||
|
@ -681,37 +869,36 @@ def print_if_set(varname, value):
|
|||
if value is not None:
|
||||
print(varname, value)
|
||||
|
||||
libdir = get_config_var("LIBDIR")
|
||||
# Windows always uses shared linking
|
||||
WINDOWS = hasattr(platform, "win32_ver")
|
||||
|
||||
# macOS framework packages use shared linking
|
||||
FRAMEWORK = bool(get_config_var("PYTHONFRAMEWORK"))
|
||||
|
||||
# unix-style shared library enabled
|
||||
SHARED = bool(get_config_var("Py_ENABLE_SHARED"))
|
||||
|
||||
print("version_major", sys.version_info[0])
|
||||
print("version_minor", sys.version_info[1])
|
||||
print("implementation", platform.python_implementation())
|
||||
print_if_set("libdir", libdir)
|
||||
print_if_set("libdir", get_config_var("LIBDIR"))
|
||||
print_if_set("ld_version", get_config_var("LDVERSION"))
|
||||
print_if_set("base_prefix", base_prefix)
|
||||
print("framework", bool(get_config_var("PYTHONFRAMEWORK")))
|
||||
print("shared", PYPY or ANACONDA or bool(get_config_var("Py_ENABLE_SHARED")))
|
||||
print("shared", PYPY or ANACONDA or WINDOWS or FRAMEWORK or SHARED)
|
||||
print("executable", sys.executable)
|
||||
print("calcsize_pointer", struct.calcsize("P"))
|
||||
"#;
|
||||
let output = run_python_script(interpreter, script)?;
|
||||
let map: HashMap<String, String> = parse_script_output(&output);
|
||||
let shared = match (
|
||||
cargo_env_var("CARGO_CFG_TARGET_OS").unwrap().as_str(),
|
||||
map["framework"].as_str(),
|
||||
map["shared"].as_str(),
|
||||
) {
|
||||
(_, _, "True") // Py_ENABLE_SHARED is set
|
||||
| ("windows", _, _) // Windows always uses shared linking
|
||||
| ("macos", "True", _) // MacOS framework package uses shared linking
|
||||
=> true,
|
||||
(_, _, "False") => false, // Any other platform, Py_ENABLE_SHARED not set
|
||||
_ => bail!("Unrecognised link model combination")
|
||||
};
|
||||
let shared = map["shared"].as_str() == "True";
|
||||
|
||||
let version = PythonVersion {
|
||||
major: map["version_major"].parse()?,
|
||||
minor: map["version_minor"].parse()?,
|
||||
major: map["version_major"]
|
||||
.parse()
|
||||
.context("failed to parse major version")?,
|
||||
minor: map["version_minor"]
|
||||
.parse()
|
||||
.context("failed to parse minor version")?,
|
||||
};
|
||||
|
||||
let implementation = map["implementation"].parse()?;
|
||||
|
@ -725,7 +912,11 @@ print("calcsize_pointer", struct.calcsize("P"))
|
|||
ld_version: map.get("ld_version").cloned(),
|
||||
base_prefix: map.get("base_prefix").cloned(),
|
||||
executable: map.get("executable").cloned(),
|
||||
calcsize_pointer: Some(map["calcsize_pointer"].parse()?),
|
||||
calcsize_pointer: Some(
|
||||
map["calcsize_pointer"]
|
||||
.parse()
|
||||
.context("failed to parse calcsize_pointer")?,
|
||||
),
|
||||
build_flags: BuildFlags::from_interpreter(interpreter)?.fixup(version, implementation),
|
||||
})
|
||||
}
|
||||
|
@ -735,7 +926,7 @@ fn get_abi3_minor_version() -> Option<u8> {
|
|||
.find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some())
|
||||
}
|
||||
|
||||
fn get_interpreter_config() -> Result<InterpreterConfig> {
|
||||
pub fn make_interpreter_config() -> Result<InterpreterConfig> {
|
||||
let abi3_version = get_abi3_minor_version();
|
||||
|
||||
// If PYO3_NO_PYTHON is set with abi3, we can build PyO3 without calling Python.
|
||||
|
@ -780,53 +971,125 @@ fn get_interpreter_config() -> Result<InterpreterConfig> {
|
|||
Ok(interpreter_config)
|
||||
}
|
||||
|
||||
pub fn configure() -> Result<()> {
|
||||
let interpreter_config = get_interpreter_config()?;
|
||||
write_interpreter_config(&interpreter_config)
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Cursor;
|
||||
|
||||
fn write_interpreter_config(interpreter_config: &InterpreterConfig) -> Result<()> {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let mut out = File::create(Path::new(&out_dir).join("pyo3-build-config.rs"))?;
|
||||
use super::*;
|
||||
|
||||
writeln!(out, "{{")?;
|
||||
writeln!(
|
||||
out,
|
||||
"let mut build_flags = std::collections::HashSet::new();"
|
||||
)?;
|
||||
for flag in &interpreter_config.build_flags.0 {
|
||||
writeln!(out, "build_flags.insert({:?});", flag)?;
|
||||
#[test]
|
||||
fn test_read_write_roundtrip() {
|
||||
let config = InterpreterConfig {
|
||||
abi3: true,
|
||||
base_prefix: Some("base_prefix".into()),
|
||||
build_flags: BuildFlags::abi3(),
|
||||
calcsize_pointer: Some(32),
|
||||
executable: Some("executable".into()),
|
||||
implementation: PythonImplementation::CPython,
|
||||
ld_version: Some("ld_version".into()),
|
||||
libdir: Some("libdir".into()),
|
||||
shared: true,
|
||||
version: MINIMUM_SUPPORTED_VERSION,
|
||||
};
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
config.to_writer(&mut buf).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
config,
|
||||
InterpreterConfig::from_reader(Cursor::new(buf)).unwrap()
|
||||
);
|
||||
|
||||
// And some different options, for variety
|
||||
|
||||
let config = InterpreterConfig {
|
||||
abi3: false,
|
||||
base_prefix: None,
|
||||
build_flags: {
|
||||
let mut flags = HashSet::new();
|
||||
flags.insert(BuildFlag::Py_DEBUG);
|
||||
flags.insert(BuildFlag::Other(String::from("Py_SOME_FLAG")));
|
||||
BuildFlags(flags)
|
||||
},
|
||||
calcsize_pointer: None,
|
||||
executable: None,
|
||||
implementation: PythonImplementation::PyPy,
|
||||
ld_version: None,
|
||||
libdir: None,
|
||||
shared: true,
|
||||
version: PythonVersion {
|
||||
major: 3,
|
||||
minor: 10,
|
||||
},
|
||||
};
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
config.to_writer(&mut buf).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
config,
|
||||
InterpreterConfig::from_reader(Cursor::new(buf)).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
writeln!(
|
||||
out,
|
||||
r#"crate::impl_::InterpreterConfig {{
|
||||
version: crate::impl_::PythonVersion {{
|
||||
major: {major},
|
||||
minor: {minor},
|
||||
}},
|
||||
implementation: crate::impl_::PythonImplementation::{implementation:?},
|
||||
libdir: {libdir:?}.map(|str: &str| str.to_string()),
|
||||
abi3: {abi3},
|
||||
build_flags: crate::impl_::BuildFlags(build_flags),
|
||||
base_prefix: {base_prefix:?}.map(|str: &str| str.to_string()),
|
||||
calcsize_pointer: {calcsize_pointer:?},
|
||||
executable: {executable:?}.map(|str: &str| str.to_string()),
|
||||
ld_version: {ld_version:?}.map(|str: &str| str.to_string()),
|
||||
shared: {shared:?}
|
||||
}}"#,
|
||||
major = interpreter_config.version.major,
|
||||
minor = interpreter_config.version.minor,
|
||||
implementation = interpreter_config.implementation,
|
||||
base_prefix = interpreter_config.base_prefix,
|
||||
calcsize_pointer = interpreter_config.calcsize_pointer,
|
||||
executable = interpreter_config.executable,
|
||||
ld_version = interpreter_config.ld_version,
|
||||
libdir = interpreter_config.libdir,
|
||||
shared = interpreter_config.shared,
|
||||
abi3 = interpreter_config.abi3,
|
||||
)?;
|
||||
writeln!(out, "}}")?;
|
||||
#[test]
|
||||
fn build_flags_from_config_map() {
|
||||
let mut config_map = HashMap::new();
|
||||
|
||||
Ok(())
|
||||
assert_eq!(BuildFlags::from_config_map(&config_map).0, HashSet::new());
|
||||
|
||||
for flag in &BuildFlags::ALL {
|
||||
config_map.insert(flag.to_string(), "0".into());
|
||||
}
|
||||
|
||||
assert_eq!(BuildFlags::from_config_map(&config_map).0, HashSet::new());
|
||||
|
||||
let mut expected_flags = HashSet::new();
|
||||
for flag in &BuildFlags::ALL {
|
||||
config_map.insert(flag.to_string(), "1".into());
|
||||
expected_flags.insert(flag.clone());
|
||||
}
|
||||
|
||||
assert_eq!(BuildFlags::from_config_map(&config_map).0, expected_flags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_flags_fixup_py36_debug() {
|
||||
let mut build_flags = BuildFlags(HashSet::new());
|
||||
build_flags.0.insert(BuildFlag::Py_DEBUG);
|
||||
|
||||
build_flags = build_flags.fixup(
|
||||
PythonVersion { major: 3, minor: 6 },
|
||||
PythonImplementation::CPython,
|
||||
);
|
||||
|
||||
// On 3.6, Py_DEBUG implies Py_REF_DEBUG and Py_TRACE_REFS
|
||||
assert!(build_flags.0.contains(&BuildFlag::Py_REF_DEBUG));
|
||||
assert!(build_flags.0.contains(&BuildFlag::Py_TRACE_REFS));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_flags_fixup_py37_debug() {
|
||||
let mut build_flags = BuildFlags(HashSet::new());
|
||||
build_flags.0.insert(BuildFlag::Py_DEBUG);
|
||||
|
||||
build_flags = build_flags.fixup(PythonVersion::PY37, PythonImplementation::CPython);
|
||||
|
||||
// On 3.7, Py_DEBUG implies Py_REF_DEBUG
|
||||
assert!(build_flags.0.contains(&BuildFlag::Py_REF_DEBUG));
|
||||
|
||||
// 3.7 always has WITH_THREAD
|
||||
assert!(build_flags.0.contains(&BuildFlag::WITH_THREAD));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_flags_fixup_pypy() {
|
||||
let mut build_flags = BuildFlags(HashSet::new());
|
||||
|
||||
build_flags = build_flags.fixup(
|
||||
PythonVersion { major: 3, minor: 6 },
|
||||
PythonImplementation::PyPy,
|
||||
);
|
||||
|
||||
// PyPy always has WITH_THREAD
|
||||
assert!(build_flags.0.contains(&BuildFlag::WITH_THREAD));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,19 +12,40 @@
|
|||
//! | `#[cfg(Py_LIMITED_API)]` | This marks code which is run when compiling with PyO3's `abi3` feature enabled. |
|
||||
//! | `#[cfg(PyPy)]` | This marks code which is run when compiling for PyPy. |
|
||||
//!
|
||||
//! For examples of how to use these attributes, [see PyO3's guide](https://pyo3.rs/main/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).
|
||||
|
||||
#[allow(dead_code)] // TODO cover this using tests
|
||||
#[doc(hidden)]
|
||||
pub mod errors;
|
||||
mod impl_;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use crate::impl_::{InterpreterConfig, PythonImplementation, PythonVersion};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
pub use impl_::{
|
||||
find_interpreter, get_config_from_interpreter, InterpreterConfig, PythonImplementation,
|
||||
PythonVersion,
|
||||
};
|
||||
|
||||
// Used in PyO3's build.rs
|
||||
#[doc(hidden)]
|
||||
pub fn get() -> InterpreterConfig {
|
||||
include!(concat!(env!("OUT_DIR"), "/pyo3-build-config.rs"))
|
||||
pub use impl_::make_interpreter_config;
|
||||
|
||||
/// Reads the configuration written by PyO3's build.rs
|
||||
///
|
||||
/// Because this will never change in a given compilation run, this is cached in a `once_cell`.
|
||||
#[doc(hidden)]
|
||||
pub fn get() -> &'static InterpreterConfig {
|
||||
static CONFIG: OnceCell<InterpreterConfig> = OnceCell::new();
|
||||
CONFIG.get_or_init(|| {
|
||||
let config_file = std::fs::File::open(PATH).expect("config file missing");
|
||||
let reader = std::io::BufReader::new(config_file);
|
||||
InterpreterConfig::from_reader(reader).expect("failed to parse config file")
|
||||
})
|
||||
}
|
||||
|
||||
/// Path where PyO3's build.rs will write configuration.
|
||||
#[doc(hidden)]
|
||||
pub const PATH: &str = concat!(env!("OUT_DIR"), "/pyo3-build-config.txt");
|
||||
|
||||
pub fn use_pyo3_cfgs() {
|
||||
get().emit_pyo3_cfgs();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.13.2"
|
||||
version = "0.14.1"
|
||||
description = "Code generation for PyO3 package"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||
|
@ -16,11 +16,9 @@ edition = "2018"
|
|||
[dependencies]
|
||||
quote = { version = "1", default-features = false }
|
||||
proc-macro2 = { version = "1", default-features = false }
|
||||
pyo3-build-config = { path = "../pyo3-build-config", version = "0.14.1" }
|
||||
|
||||
[dependencies.syn]
|
||||
version = "1"
|
||||
default-features = false
|
||||
features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"]
|
||||
|
||||
[build-dependencies]
|
||||
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.14.0-alpha.0" }
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
pyo3_build_config::use_pyo3_cfgs();
|
||||
}
|
|
@ -12,10 +12,13 @@ pub mod kw {
|
|||
syn::custom_keyword!(annotation);
|
||||
syn::custom_keyword!(attribute);
|
||||
syn::custom_keyword!(from_py_with);
|
||||
syn::custom_keyword!(get);
|
||||
syn::custom_keyword!(item);
|
||||
syn::custom_keyword!(pass_module);
|
||||
syn::custom_keyword!(name);
|
||||
syn::custom_keyword!(set);
|
||||
syn::custom_keyword!(signature);
|
||||
syn::custom_keyword!(text_signature);
|
||||
syn::custom_keyword!(transparent);
|
||||
}
|
||||
|
||||
|
@ -43,9 +46,24 @@ impl Parse for NameAttribute {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_pyo3_attributes<T: Parse>(
|
||||
attr: &syn::Attribute,
|
||||
) -> Result<Option<Punctuated<T, Comma>>> {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TextSignatureAttribute {
|
||||
pub kw: kw::text_signature,
|
||||
pub eq_token: Token![=],
|
||||
pub lit: LitStr,
|
||||
}
|
||||
|
||||
impl Parse for TextSignatureAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
Ok(TextSignatureAttribute {
|
||||
kw: input.parse()?,
|
||||
eq_token: input.parse()?,
|
||||
lit: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_pyo3_options<T: Parse>(attr: &syn::Attribute) -> Result<Option<Punctuated<T, Comma>>> {
|
||||
if is_attribute_ident(attr, "pyo3") {
|
||||
attr.parse_args_with(Punctuated::parse_terminated).map(Some)
|
||||
} else {
|
||||
|
@ -83,6 +101,19 @@ pub fn take_attributes(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn take_pyo3_options<T: Parse>(attrs: &mut Vec<syn::Attribute>) -> Result<Vec<T>> {
|
||||
let mut out = Vec::new();
|
||||
take_attributes(attrs, |attr| {
|
||||
if let Some(options) = get_pyo3_options(attr)? {
|
||||
out.extend(options.into_iter());
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn get_deprecated_name_attribute(
|
||||
attr: &syn::Attribute,
|
||||
deprecations: &mut Deprecations,
|
||||
|
@ -99,3 +130,49 @@ pub fn get_deprecated_name_attribute(
|
|||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_deprecated_text_signature_attribute(
|
||||
attr: &syn::Attribute,
|
||||
deprecations: &mut Deprecations,
|
||||
) -> syn::Result<Option<TextSignatureAttribute>> {
|
||||
match attr.parse_meta() {
|
||||
Ok(syn::Meta::NameValue(syn::MetaNameValue {
|
||||
path,
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
})) if path.is_ident("text_signature") => {
|
||||
let text_signature = TextSignatureAttribute {
|
||||
kw: syn::parse_quote!(text_signature),
|
||||
eq_token: syn::parse_quote!(=),
|
||||
lit,
|
||||
};
|
||||
deprecations.push(
|
||||
crate::deprecations::Deprecation::TextSignatureAttribute,
|
||||
attr.span(),
|
||||
);
|
||||
Ok(Some(text_signature))
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_deprecated_text_signature_attribute(
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
deprecations: &mut Deprecations,
|
||||
) -> syn::Result<Option<TextSignatureAttribute>> {
|
||||
let mut text_signature = None;
|
||||
let mut attrs_out = Vec::with_capacity(attrs.len());
|
||||
for attr in attrs.drain(..) {
|
||||
if let Some(value) = get_deprecated_text_signature_attribute(&attr, deprecations)? {
|
||||
ensure_spanned!(
|
||||
text_signature.is_none(),
|
||||
attr.span() => "text_signature attribute already specified previously"
|
||||
);
|
||||
text_signature = Some(value);
|
||||
} else {
|
||||
attrs_out.push(attr);
|
||||
}
|
||||
}
|
||||
*attrs = attrs_out;
|
||||
Ok(text_signature)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ use quote::{quote_spanned, ToTokens};
|
|||
pub enum Deprecation {
|
||||
NameAttribute,
|
||||
PyfnNameArgument,
|
||||
PyModuleNameArgument,
|
||||
TextSignatureAttribute,
|
||||
}
|
||||
|
||||
impl Deprecation {
|
||||
|
@ -11,6 +13,8 @@ impl Deprecation {
|
|||
let string = match self {
|
||||
Deprecation::NameAttribute => "NAME_ATTRIBUTE",
|
||||
Deprecation::PyfnNameArgument => "PYFN_NAME_ARGUMENT",
|
||||
Deprecation::PyModuleNameArgument => "PYMODULE_NAME_ARGUMENT",
|
||||
Deprecation::TextSignatureAttribute => "TEXT_SIGNATURE_ATTRIBUTE",
|
||||
};
|
||||
syn::Ident::new(string, span)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::attributes::{self, get_pyo3_attributes, FromPyWithAttribute};
|
||||
use crate::attributes::{self, get_pyo3_options, FromPyWithAttribute};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
|
@ -195,7 +195,7 @@ impl<'a> Container<'a> {
|
|||
/// Build derivation body for a struct.
|
||||
fn build(&self) -> TokenStream {
|
||||
match &self.ty {
|
||||
ContainerType::StructNewtype(ident) => self.build_newtype_struct(Some(&ident)),
|
||||
ContainerType::StructNewtype(ident) => self.build_newtype_struct(Some(ident)),
|
||||
ContainerType::TupleNewtype => self.build_newtype_struct(None),
|
||||
ContainerType::Tuple(len) => self.build_tuple_struct(*len),
|
||||
ContainerType::Struct(tups) => self.build_struct(tups),
|
||||
|
@ -353,7 +353,7 @@ impl ContainerOptions {
|
|||
annotation: None,
|
||||
};
|
||||
for attr in attrs {
|
||||
if let Some(pyo3_attrs) = get_pyo3_attributes(attr)? {
|
||||
if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
|
||||
for pyo3_attr in pyo3_attrs {
|
||||
match pyo3_attr {
|
||||
ContainerPyO3Attribute::Transparent(kw) => {
|
||||
|
@ -451,7 +451,7 @@ impl FieldPyO3Attributes {
|
|||
let mut from_py_with = None;
|
||||
|
||||
for attr in attrs {
|
||||
if let Some(pyo3_attrs) = get_pyo3_attributes(attr)? {
|
||||
if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
|
||||
for pyo3_attr in pyo3_attrs {
|
||||
match pyo3_attr {
|
||||
FieldPyO3Attribute::Getter(field_getter) => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
attributes::{
|
||||
self, get_deprecated_name_attribute, get_pyo3_attributes, is_attribute_ident,
|
||||
take_attributes, NameAttribute,
|
||||
self, get_deprecated_name_attribute, get_pyo3_options, is_attribute_ident, take_attributes,
|
||||
NameAttribute,
|
||||
},
|
||||
deprecations::Deprecations,
|
||||
};
|
||||
|
@ -69,7 +69,7 @@ impl ConstAttributes {
|
|||
);
|
||||
attributes.is_class_attr = true;
|
||||
Ok(true)
|
||||
} else if let Some(pyo3_attributes) = get_pyo3_attributes(attr)? {
|
||||
} else if let Some(pyo3_attributes) = get_pyo3_options(attr)? {
|
||||
for pyo3_attr in pyo3_attributes {
|
||||
match pyo3_attr {
|
||||
PyO3ConstAttribute::Name(name) => attributes.set_name(name)?,
|
||||
|
|
|
@ -15,6 +15,7 @@ mod from_pyobject;
|
|||
mod konst;
|
||||
mod method;
|
||||
mod module;
|
||||
mod params;
|
||||
mod proto_method;
|
||||
mod pyclass;
|
||||
mod pyfunction;
|
||||
|
@ -23,7 +24,7 @@ mod pymethod;
|
|||
mod pyproto;
|
||||
|
||||
pub use from_pyobject::build_derive_from_pyobject;
|
||||
pub use module::{process_functions_in_module, py_init};
|
||||
pub use module::{process_functions_in_module, py_init, PyModuleOptions};
|
||||
pub use pyclass::{build_py_class, PyClassArgs};
|
||||
pub use pyfunction::{build_py_function, PyFunctionOptions};
|
||||
pub use pyimpl::{build_py_methods, PyClassMethodsType};
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use crate::attributes::TextSignatureAttribute;
|
||||
use crate::params::{accept_args_kwargs, impl_arg_params};
|
||||
use crate::pyfunction::PyFunctionOptions;
|
||||
use crate::pyfunction::{PyFunctionArgPyO3Attributes, PyFunctionSignature};
|
||||
use crate::utils;
|
||||
use crate::{deprecations::Deprecations, pyfunction::Argument};
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::ToTokens;
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::ext::IdentExt;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::Result;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct FnArg<'a> {
|
||||
|
@ -23,7 +26,7 @@ pub struct FnArg<'a> {
|
|||
|
||||
impl<'a> FnArg<'a> {
|
||||
/// Transforms a rust fn arg parsed with syn into a method::FnArg
|
||||
pub fn parse(arg: &'a mut syn::FnArg) -> syn::Result<Self> {
|
||||
pub fn parse(arg: &'a mut syn::FnArg) -> Result<Self> {
|
||||
match arg {
|
||||
syn::FnArg::Receiver(recv) => {
|
||||
bail_spanned!(recv.span() => "unexpected receiver")
|
||||
|
@ -85,13 +88,44 @@ pub enum FnType {
|
|||
FnNew,
|
||||
FnClass,
|
||||
FnStatic,
|
||||
FnModule,
|
||||
ClassAttribute,
|
||||
}
|
||||
|
||||
impl FnType {
|
||||
pub fn self_conversion(&self, cls: Option<&syn::Type>) -> TokenStream {
|
||||
match self {
|
||||
FnType::Getter(st) | FnType::Setter(st) | FnType::Fn(st) | FnType::FnCall(st) => {
|
||||
st.receiver(cls.expect("no class given for Fn with a \"self\" receiver"))
|
||||
}
|
||||
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => {
|
||||
quote!()
|
||||
}
|
||||
FnType::FnClass => {
|
||||
quote! {
|
||||
let _slf = pyo3::types::PyType::from_type_ptr(_py, _slf as *mut pyo3::ffi::PyTypeObject);
|
||||
}
|
||||
}
|
||||
FnType::FnModule => {
|
||||
quote! {
|
||||
let _slf = _py.from_borrowed_ptr::<pyo3::types::PyModule>(_slf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn self_arg(&self) -> TokenStream {
|
||||
match self {
|
||||
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => quote!(),
|
||||
_ => quote!(_slf,),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SelfType {
|
||||
Receiver { mutable: bool },
|
||||
TryFromPyCell(proc_macro2::Span),
|
||||
TryFromPyCell(Span),
|
||||
}
|
||||
|
||||
impl SelfType {
|
||||
|
@ -122,6 +156,42 @@ impl SelfType {
|
|||
}
|
||||
}
|
||||
|
||||
/// Determines which CPython calling convention a given FnSpec uses.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CallingConvention {
|
||||
Noargs, // METH_NOARGS
|
||||
Varargs, // METH_VARARGS | METH_KEYWORDS
|
||||
Fastcall, // METH_FASTCALL | METH_KEYWORDS (Py3.7+ and !abi3)
|
||||
TpNew, // special convention for tp_new
|
||||
}
|
||||
|
||||
impl CallingConvention {
|
||||
/// Determine default calling convention from an argument signature.
|
||||
///
|
||||
/// Different other slots (tp_call, tp_new) can have other requirements
|
||||
/// and are set manually (see `parse_fn_type` below).
|
||||
pub fn from_args(args: &[FnArg<'_>], attrs: &[Argument]) -> Self {
|
||||
let (_, accept_kwargs) = accept_args_kwargs(attrs);
|
||||
if args.is_empty() {
|
||||
Self::Noargs
|
||||
} else if accept_kwargs {
|
||||
// for functions that accept **kwargs, always prefer varargs
|
||||
Self::Varargs
|
||||
} else if can_use_fastcall() {
|
||||
Self::Fastcall
|
||||
} else {
|
||||
Self::Varargs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn can_use_fastcall() -> bool {
|
||||
const PY37: pyo3_build_config::PythonVersion =
|
||||
pyo3_build_config::PythonVersion { major: 3, minor: 7 };
|
||||
let config = pyo3_build_config::get();
|
||||
config.version >= PY37 && !config.abi3
|
||||
}
|
||||
|
||||
pub struct FnSpec<'a> {
|
||||
pub tp: FnType,
|
||||
// Rust function name
|
||||
|
@ -134,6 +204,7 @@ pub struct FnSpec<'a> {
|
|||
pub output: syn::Type,
|
||||
pub doc: syn::LitStr,
|
||||
pub deprecations: Deprecations,
|
||||
pub convention: CallingConvention,
|
||||
}
|
||||
|
||||
pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
|
||||
|
@ -143,7 +214,7 @@ pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse_method_receiver(arg: &syn::FnArg) -> syn::Result<SelfType> {
|
||||
pub fn parse_method_receiver(arg: &syn::FnArg) -> Result<SelfType> {
|
||||
match arg {
|
||||
syn::FnArg::Receiver(recv) => Ok(SelfType::Receiver {
|
||||
mutable: recv.mutability.is_some(),
|
||||
|
@ -163,7 +234,7 @@ impl<'a> FnSpec<'a> {
|
|||
sig: &'a mut syn::Signature,
|
||||
meth_attrs: &mut Vec<syn::Attribute>,
|
||||
options: PyFunctionOptions,
|
||||
) -> syn::Result<FnSpec<'a>> {
|
||||
) -> Result<FnSpec<'a>> {
|
||||
let MethodAttributes {
|
||||
ty: fn_type_attr,
|
||||
args: fn_attrs,
|
||||
|
@ -175,42 +246,53 @@ impl<'a> FnSpec<'a> {
|
|||
if let Some(name) = &python_name {
|
||||
bail_spanned!(name.span() => "`name` not allowed with `#[new]`");
|
||||
}
|
||||
python_name = Some(syn::Ident::new("__new__", proc_macro2::Span::call_site()))
|
||||
python_name = Some(syn::Ident::new("__new__", Span::call_site()))
|
||||
}
|
||||
Some(MethodTypeAttribute::Call) => {
|
||||
if let Some(name) = &python_name {
|
||||
bail_spanned!(name.span() => "`name` not allowed with `#[call]`");
|
||||
}
|
||||
python_name = Some(syn::Ident::new("__call__", proc_macro2::Span::call_site()))
|
||||
python_name = Some(syn::Ident::new("__call__", Span::call_site()))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let (fn_type, skip_first_arg) = Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;
|
||||
let (fn_type, skip_first_arg, fixed_convention) =
|
||||
Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;
|
||||
Self::ensure_text_signature_on_valid_method(&fn_type, options.text_signature.as_ref())?;
|
||||
|
||||
let name = &sig.ident;
|
||||
let ty = get_return_info(&sig.output);
|
||||
let python_name = python_name.as_ref().unwrap_or(name).unraw();
|
||||
|
||||
let text_signature = Self::parse_text_signature(meth_attrs, &fn_type, &python_name)?;
|
||||
let doc = utils::get_doc(&meth_attrs, text_signature, true)?;
|
||||
let doc = utils::get_doc(
|
||||
meth_attrs,
|
||||
options
|
||||
.text_signature
|
||||
.as_ref()
|
||||
.map(|attr| (&python_name, attr)),
|
||||
)?;
|
||||
|
||||
let arguments = if skip_first_arg {
|
||||
let arguments: Vec<_> = if skip_first_arg {
|
||||
sig.inputs
|
||||
.iter_mut()
|
||||
.skip(1)
|
||||
.map(FnArg::parse)
|
||||
.collect::<syn::Result<_>>()?
|
||||
.collect::<Result<_>>()?
|
||||
} else {
|
||||
sig.inputs
|
||||
.iter_mut()
|
||||
.map(FnArg::parse)
|
||||
.collect::<syn::Result<_>>()?
|
||||
.collect::<Result<_>>()?
|
||||
};
|
||||
|
||||
let convention =
|
||||
fixed_convention.unwrap_or_else(|| CallingConvention::from_args(&arguments, &fn_attrs));
|
||||
|
||||
Ok(FnSpec {
|
||||
tp: fn_type,
|
||||
name,
|
||||
convention,
|
||||
python_name,
|
||||
attrs: fn_attrs,
|
||||
args: arguments,
|
||||
|
@ -220,48 +302,38 @@ impl<'a> FnSpec<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn null_terminated_python_name(&self) -> TokenStream {
|
||||
let name = format!("{}\0", self.python_name);
|
||||
quote!({#name})
|
||||
pub fn null_terminated_python_name(&self) -> syn::LitStr {
|
||||
syn::LitStr::new(&format!("{}\0", self.python_name), self.python_name.span())
|
||||
}
|
||||
|
||||
fn parse_text_signature(
|
||||
meth_attrs: &mut Vec<syn::Attribute>,
|
||||
fn ensure_text_signature_on_valid_method(
|
||||
fn_type: &FnType,
|
||||
python_name: &syn::Ident,
|
||||
) -> syn::Result<Option<syn::LitStr>> {
|
||||
let mut parse_erroneous_text_signature = |error_msg: &str| {
|
||||
// try to parse anyway to give better error messages
|
||||
if let Some(text_signature) =
|
||||
utils::parse_text_signature_attrs(meth_attrs, &python_name)?
|
||||
{
|
||||
bail_spanned!(text_signature.span() => error_msg)
|
||||
} else {
|
||||
Ok(None)
|
||||
text_signature: Option<&TextSignatureAttribute>,
|
||||
) -> syn::Result<()> {
|
||||
if let Some(text_signature) = text_signature {
|
||||
match &fn_type {
|
||||
FnType::FnNew => bail_spanned!(
|
||||
text_signature.kw.span() =>
|
||||
"text_signature not allowed on __new__; if you want to add a signature on \
|
||||
__new__, put it on the struct definition instead"
|
||||
),
|
||||
FnType::FnCall(_)
|
||||
| FnType::Getter(_)
|
||||
| FnType::Setter(_)
|
||||
| FnType::ClassAttribute => bail_spanned!(
|
||||
text_signature.kw.span() => "text_signature not allowed with this method type"
|
||||
),
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
|
||||
let text_signature = match &fn_type {
|
||||
FnType::Fn(_) | FnType::FnClass | FnType::FnStatic => {
|
||||
utils::parse_text_signature_attrs(&mut *meth_attrs, &python_name)?
|
||||
}
|
||||
FnType::FnNew => parse_erroneous_text_signature(
|
||||
"text_signature not allowed on __new__; if you want to add a signature on \
|
||||
__new__, put it on the struct definition instead",
|
||||
)?,
|
||||
FnType::FnCall(_) | FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => {
|
||||
parse_erroneous_text_signature("text_signature not allowed with this method type")?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(text_signature)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_fn_type(
|
||||
sig: &syn::Signature,
|
||||
fn_type_attr: Option<MethodTypeAttribute>,
|
||||
python_name: &mut Option<syn::Ident>,
|
||||
) -> syn::Result<(FnType, bool)> {
|
||||
) -> Result<(FnType, bool, Option<CallingConvention>)> {
|
||||
let name = &sig.ident;
|
||||
let parse_receiver = |msg: &'static str| {
|
||||
let first_arg = sig
|
||||
|
@ -282,20 +354,23 @@ impl<'a> FnSpec<'a> {
|
|||
}
|
||||
};
|
||||
|
||||
let (fn_type, skip_first_arg) = match fn_type_attr {
|
||||
Some(MethodTypeAttribute::StaticMethod) => (FnType::FnStatic, false),
|
||||
let (fn_type, skip_first_arg, fixed_convention) = match fn_type_attr {
|
||||
Some(MethodTypeAttribute::StaticMethod) => (FnType::FnStatic, false, None),
|
||||
Some(MethodTypeAttribute::ClassAttribute) => {
|
||||
ensure_spanned!(
|
||||
sig.inputs.is_empty(),
|
||||
sig.inputs.span() => "class attribute methods cannot take arguments"
|
||||
);
|
||||
(FnType::ClassAttribute, false)
|
||||
(FnType::ClassAttribute, false, None)
|
||||
}
|
||||
Some(MethodTypeAttribute::New) => (FnType::FnNew, false),
|
||||
Some(MethodTypeAttribute::ClassMethod) => (FnType::FnClass, true),
|
||||
Some(MethodTypeAttribute::New) => {
|
||||
(FnType::FnNew, false, Some(CallingConvention::TpNew))
|
||||
}
|
||||
Some(MethodTypeAttribute::ClassMethod) => (FnType::FnClass, true, None),
|
||||
Some(MethodTypeAttribute::Call) => (
|
||||
FnType::FnCall(parse_receiver("expected receiver for #[call]")?),
|
||||
true,
|
||||
Some(CallingConvention::Varargs),
|
||||
),
|
||||
Some(MethodTypeAttribute::Getter) => {
|
||||
// Strip off "get_" prefix if needed
|
||||
|
@ -306,6 +381,7 @@ impl<'a> FnSpec<'a> {
|
|||
(
|
||||
FnType::Getter(parse_receiver("expected receiver for #[getter]")?),
|
||||
true,
|
||||
None,
|
||||
)
|
||||
}
|
||||
Some(MethodTypeAttribute::Setter) => {
|
||||
|
@ -317,6 +393,7 @@ impl<'a> FnSpec<'a> {
|
|||
(
|
||||
FnType::Setter(parse_receiver("expected receiver for #[setter]")?),
|
||||
true,
|
||||
None,
|
||||
)
|
||||
}
|
||||
None => (
|
||||
|
@ -324,27 +401,10 @@ impl<'a> FnSpec<'a> {
|
|||
"static method needs #[staticmethod] attribute",
|
||||
)?),
|
||||
true,
|
||||
None,
|
||||
),
|
||||
};
|
||||
Ok((fn_type, skip_first_arg))
|
||||
}
|
||||
|
||||
pub fn is_args(&self, name: &syn::Ident) -> bool {
|
||||
for s in self.attrs.iter() {
|
||||
if let Argument::VarArgs(path) = s {
|
||||
return path.is_ident(name);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_kwargs(&self, name: &syn::Ident) -> bool {
|
||||
for s in self.attrs.iter() {
|
||||
if let Argument::KeywordArgs(path) = s {
|
||||
return path.is_ident(name);
|
||||
}
|
||||
}
|
||||
false
|
||||
Ok((fn_type, skip_first_arg, fixed_convention))
|
||||
}
|
||||
|
||||
pub fn default_value(&self, name: &syn::Ident) -> Option<TokenStream> {
|
||||
|
@ -353,7 +413,7 @@ impl<'a> FnSpec<'a> {
|
|||
Argument::Arg(path, opt) | Argument::Kwarg(path, opt) => {
|
||||
if path.is_ident(name) {
|
||||
if let Some(val) = opt {
|
||||
let i: syn::Expr = syn::parse_str(&val).unwrap();
|
||||
let i: syn::Expr = syn::parse_str(val).unwrap();
|
||||
return Some(i.into_token_stream());
|
||||
}
|
||||
}
|
||||
|
@ -374,6 +434,146 @@ impl<'a> FnSpec<'a> {
|
|||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Return a C wrapper function for this signature.
|
||||
pub fn get_wrapper_function(
|
||||
&self,
|
||||
ident: &proc_macro2::Ident,
|
||||
cls: Option<&syn::Type>,
|
||||
) -> Result<TokenStream> {
|
||||
let deprecations = &self.deprecations;
|
||||
let self_conversion = self.tp.self_conversion(cls);
|
||||
let self_arg = self.tp.self_arg();
|
||||
let arg_names = (0..self.args.len())
|
||||
.map(|pos| syn::Ident::new(&format!("arg{}", pos), Span::call_site()))
|
||||
.collect::<Vec<_>>();
|
||||
let py = syn::Ident::new("_py", Span::call_site());
|
||||
let func_name = &self.name;
|
||||
let rust_name = if let Some(cls) = cls {
|
||||
quote!(#cls::#func_name)
|
||||
} else {
|
||||
quote!(#func_name)
|
||||
};
|
||||
let rust_call =
|
||||
quote! { pyo3::callback::convert(#py, #rust_name(#self_arg #(#arg_names),*)) };
|
||||
Ok(match self.convention {
|
||||
CallingConvention::Noargs => {
|
||||
quote! {
|
||||
unsafe extern "C" fn #ident (
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_args: *mut pyo3::ffi::PyObject,
|
||||
) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
#deprecations
|
||||
pyo3::callback::handle_panic(|#py| {
|
||||
#self_conversion
|
||||
#rust_call
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
CallingConvention::Fastcall => {
|
||||
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, true)?;
|
||||
quote! {
|
||||
unsafe extern "C" fn #ident (
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_args: *const *mut pyo3::ffi::PyObject,
|
||||
_nargs: pyo3::ffi::Py_ssize_t,
|
||||
_kwnames: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
#deprecations
|
||||
pyo3::callback::handle_panic(|#py| {
|
||||
#self_conversion
|
||||
let _kwnames: Option<&pyo3::types::PyTuple> = #py.from_borrowed_ptr_or_opt(_kwnames);
|
||||
// Safety: &PyAny has the same memory layout as `*mut ffi::PyObject`
|
||||
let _args = _args as *const &pyo3::PyAny;
|
||||
let _kwargs = if let Some(kwnames) = _kwnames {
|
||||
std::slice::from_raw_parts(_args.offset(_nargs), kwnames.len())
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
let _args = std::slice::from_raw_parts(_args, _nargs as usize);
|
||||
|
||||
#arg_convert_and_rust_call
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
CallingConvention::Varargs => {
|
||||
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, false)?;
|
||||
quote! {
|
||||
unsafe extern "C" fn #ident (
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_args: *mut pyo3::ffi::PyObject,
|
||||
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
#deprecations
|
||||
pyo3::callback::handle_panic(|#py| {
|
||||
#self_conversion
|
||||
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
|
||||
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
|
||||
|
||||
#arg_convert_and_rust_call
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
CallingConvention::TpNew => {
|
||||
let rust_call = quote! { #rust_name(#(#arg_names),*) };
|
||||
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, false)?;
|
||||
quote! {
|
||||
unsafe extern "C" fn #ident (
|
||||
subtype: *mut pyo3::ffi::PyTypeObject,
|
||||
_args: *mut pyo3::ffi::PyObject,
|
||||
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
#deprecations
|
||||
use pyo3::callback::IntoPyCallbackOutput;
|
||||
pyo3::callback::handle_panic(|#py| {
|
||||
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
|
||||
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
|
||||
|
||||
let result = #arg_convert_and_rust_call;
|
||||
let initializer: pyo3::PyClassInitializer::<#cls> = result.convert(#py)?;
|
||||
let cell = initializer.create_cell_from_subtype(#py, subtype)?;
|
||||
Ok(cell as *mut pyo3::ffi::PyObject)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Return a `PyMethodDef` constructor for this function, matching the selected
|
||||
/// calling convention.
|
||||
pub fn get_methoddef(&self, wrapper: impl ToTokens) -> TokenStream {
|
||||
let python_name = self.null_terminated_python_name();
|
||||
let doc = &self.doc;
|
||||
match self.convention {
|
||||
CallingConvention::Noargs => quote! {
|
||||
pyo3::class::methods::PyMethodDef::noargs(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyCFunction(#wrapper),
|
||||
#doc,
|
||||
)
|
||||
},
|
||||
CallingConvention::Fastcall => quote! {
|
||||
pyo3::class::methods::PyMethodDef::fastcall_cfunction_with_keywords(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyCFunctionFastWithKeywords(#wrapper),
|
||||
#doc,
|
||||
)
|
||||
},
|
||||
CallingConvention::Varargs => quote! {
|
||||
pyo3::class::methods::PyMethodDef::cfunction_with_keywords(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
||||
#doc,
|
||||
)
|
||||
},
|
||||
CallingConvention::TpNew => unreachable!("tp_new cannot get a methoddef"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
|
@ -386,7 +586,7 @@ struct MethodAttributes {
|
|||
fn parse_method_attributes(
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
mut python_name: Option<syn::Ident>,
|
||||
) -> syn::Result<MethodAttributes> {
|
||||
) -> Result<MethodAttributes> {
|
||||
let mut new_attrs = Vec::new();
|
||||
let mut args = Vec::new();
|
||||
let mut ty: Option<MethodTypeAttribute> = None;
|
||||
|
|
|
@ -1,19 +1,72 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
//! Code generation for the function that initializes a python module and adds classes and function.
|
||||
|
||||
use crate::pyfunction::{impl_wrap_pyfunction, PyFunctionOptions};
|
||||
use crate::{
|
||||
attributes::{self, take_pyo3_options},
|
||||
deprecations::Deprecations,
|
||||
pyfunction::{impl_wrap_pyfunction, PyFunctionOptions},
|
||||
};
|
||||
use crate::{
|
||||
attributes::{is_attribute_ident, take_attributes, NameAttribute},
|
||||
deprecations::Deprecation,
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::{parse::Parse, spanned::Spanned, token::Comma, Ident, Path};
|
||||
use syn::{
|
||||
ext::IdentExt,
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
token::Comma,
|
||||
Ident, Path, Result,
|
||||
};
|
||||
|
||||
pub struct PyModuleOptions {
|
||||
name: Option<syn::Ident>,
|
||||
deprecations: Deprecations,
|
||||
}
|
||||
|
||||
impl PyModuleOptions {
|
||||
pub fn from_pymodule_arg_and_attrs(
|
||||
deprecated_pymodule_name_arg: Option<syn::Ident>,
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
) -> Result<Self> {
|
||||
let mut deprecations = Deprecations::new();
|
||||
if let Some(name) = &deprecated_pymodule_name_arg {
|
||||
deprecations.push(Deprecation::PyModuleNameArgument, name.span());
|
||||
}
|
||||
|
||||
let mut options: PyModuleOptions = PyModuleOptions {
|
||||
name: deprecated_pymodule_name_arg,
|
||||
deprecations,
|
||||
};
|
||||
|
||||
for option in take_pyo3_options(attrs)? {
|
||||
match option {
|
||||
PyModulePyO3Option::Name(name) => options.set_name(name.0)?,
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the function that is called by the python interpreter to initialize the native
|
||||
/// module
|
||||
pub fn py_init(fnname: &Ident, name: &Ident, doc: syn::LitStr) -> TokenStream {
|
||||
pub fn py_init(fnname: &Ident, options: PyModuleOptions, doc: syn::LitStr) -> TokenStream {
|
||||
let name = options.name.unwrap_or_else(|| fnname.unraw());
|
||||
let deprecations = options.deprecations;
|
||||
let cb_name = Ident::new(&format!("PyInit_{}", name), Span::call_site());
|
||||
assert!(doc.value().ends_with('\0'));
|
||||
|
||||
quote! {
|
||||
#[no_mangle]
|
||||
|
@ -23,9 +76,11 @@ pub fn py_init(fnname: &Ident, name: &Ident, doc: syn::LitStr) -> TokenStream {
|
|||
pub unsafe extern "C" fn #cb_name() -> *mut pyo3::ffi::PyObject {
|
||||
use pyo3::derive_utils::ModuleDef;
|
||||
static NAME: &str = concat!(stringify!(#name), "\0");
|
||||
static DOC: &str = concat!(#doc, "\0");
|
||||
static DOC: &str = #doc;
|
||||
static MODULE_DEF: ModuleDef = unsafe { ModuleDef::new(NAME, DOC) };
|
||||
|
||||
#deprecations
|
||||
|
||||
pyo3::callback::handle_panic(|_py| { MODULE_DEF.make_module(_py, #fnname) })
|
||||
}
|
||||
}
|
||||
|
@ -35,21 +90,19 @@ pub fn py_init(fnname: &Ident, name: &Ident, doc: syn::LitStr) -> TokenStream {
|
|||
pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> {
|
||||
let mut stmts: Vec<syn::Stmt> = Vec::new();
|
||||
|
||||
for stmt in func.block.stmts.iter_mut() {
|
||||
if let syn::Stmt::Item(syn::Item::Fn(func)) = stmt {
|
||||
for mut stmt in func.block.stmts.drain(..) {
|
||||
if let syn::Stmt::Item(syn::Item::Fn(func)) = &mut stmt {
|
||||
if let Some(pyfn_args) = get_pyfn_attr(&mut func.attrs)? {
|
||||
let module_name = pyfn_args.modname;
|
||||
let (ident, wrapped_function) = impl_wrap_pyfunction(func, pyfn_args.options)?;
|
||||
let item: syn::ItemFn = syn::parse_quote! {
|
||||
fn block_wrapper() {
|
||||
#wrapped_function
|
||||
#module_name.add_function(#ident(#module_name)?)?;
|
||||
}
|
||||
let statements: Vec<syn::Stmt> = syn::parse_quote! {
|
||||
#wrapped_function
|
||||
#module_name.add_function(#ident(#module_name)?)?;
|
||||
};
|
||||
stmts.extend(item.block.stmts.into_iter());
|
||||
stmts.extend(statements);
|
||||
}
|
||||
};
|
||||
stmts.push(stmt.clone());
|
||||
stmts.push(stmt);
|
||||
}
|
||||
|
||||
func.block.stmts = stmts;
|
||||
|
@ -114,8 +167,23 @@ fn get_pyfn_attr(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<PyFnArgs
|
|||
})?;
|
||||
|
||||
if let Some(pyfn_args) = &mut pyfn_args {
|
||||
pyfn_args.options.take_pyo3_attributes(attrs)?;
|
||||
pyfn_args.options.take_pyo3_options(attrs)?;
|
||||
}
|
||||
|
||||
Ok(pyfn_args)
|
||||
}
|
||||
|
||||
enum PyModulePyO3Option {
|
||||
Name(NameAttribute),
|
||||
}
|
||||
|
||||
impl Parse for PyModulePyO3Option {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::name) {
|
||||
input.parse().map(PyModulePyO3Option::Name)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,312 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use crate::{
|
||||
attributes::FromPyWithAttribute,
|
||||
method::{FnArg, FnSpec},
|
||||
pyfunction::Argument,
|
||||
utils::unwrap_ty_group,
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::ext::IdentExt;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::Result;
|
||||
|
||||
/// Determine if the function gets passed a *args tuple or **kwargs dict.
|
||||
pub fn accept_args_kwargs(attrs: &[Argument]) -> (bool, bool) {
|
||||
let (mut accept_args, mut accept_kwargs) = (false, false);
|
||||
|
||||
for s in attrs {
|
||||
match s {
|
||||
Argument::VarArgs(_) => accept_args = true,
|
||||
Argument::KeywordArgs(_) => accept_kwargs = true,
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
(accept_args, accept_kwargs)
|
||||
}
|
||||
|
||||
/// Return true if the argument list is simply (*args, **kwds).
|
||||
pub fn is_forwarded_args(args: &[FnArg<'_>], attrs: &[Argument]) -> bool {
|
||||
args.len() == 2 && is_args(attrs, args[0].name) && is_kwargs(attrs, args[1].name)
|
||||
}
|
||||
|
||||
fn is_args(attrs: &[Argument], name: &syn::Ident) -> bool {
|
||||
for s in attrs.iter() {
|
||||
if let Argument::VarArgs(path) = s {
|
||||
return path.is_ident(name);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn is_kwargs(attrs: &[Argument], name: &syn::Ident) -> bool {
|
||||
for s in attrs.iter() {
|
||||
if let Argument::KeywordArgs(path) = s {
|
||||
return path.is_ident(name);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn impl_arg_params(
|
||||
spec: &FnSpec<'_>,
|
||||
self_: Option<&syn::Type>,
|
||||
body: TokenStream,
|
||||
py: &syn::Ident,
|
||||
fastcall: bool,
|
||||
) -> Result<TokenStream> {
|
||||
if spec.args.is_empty() {
|
||||
return Ok(body);
|
||||
}
|
||||
|
||||
let args_array = syn::Ident::new("output", Span::call_site());
|
||||
|
||||
if !fastcall && is_forwarded_args(&spec.args, &spec.attrs) {
|
||||
// In the varargs convention, we can just pass though if the signature
|
||||
// is (*args, **kwds).
|
||||
let mut arg_convert = vec![];
|
||||
for (i, arg) in spec.args.iter().enumerate() {
|
||||
arg_convert.push(impl_arg_param(arg, spec, i, None, &mut 0, py, &args_array)?);
|
||||
}
|
||||
return Ok(quote! {{
|
||||
let _args = Some(_args);
|
||||
#(#arg_convert)*
|
||||
#body
|
||||
}});
|
||||
};
|
||||
|
||||
let mut positional_parameter_names = Vec::new();
|
||||
let mut required_positional_parameters = 0usize;
|
||||
let mut keyword_only_parameters = Vec::new();
|
||||
|
||||
for arg in spec.args.iter() {
|
||||
if arg.py || is_args(&spec.attrs, arg.name) || is_kwargs(&spec.attrs, arg.name) {
|
||||
continue;
|
||||
}
|
||||
let name = arg.name.unraw().to_string();
|
||||
let kwonly = spec.is_kw_only(arg.name);
|
||||
let required = !(arg.optional.is_some() || spec.default_value(arg.name).is_some());
|
||||
|
||||
if kwonly {
|
||||
keyword_only_parameters.push(quote! {
|
||||
pyo3::derive_utils::KeywordOnlyParameterDescription {
|
||||
name: #name,
|
||||
required: #required,
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if required {
|
||||
required_positional_parameters += 1;
|
||||
}
|
||||
positional_parameter_names.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
let num_params = positional_parameter_names.len() + keyword_only_parameters.len();
|
||||
|
||||
let mut param_conversion = Vec::new();
|
||||
let mut option_pos = 0;
|
||||
for (idx, arg) in spec.args.iter().enumerate() {
|
||||
param_conversion.push(impl_arg_param(
|
||||
arg,
|
||||
spec,
|
||||
idx,
|
||||
self_,
|
||||
&mut option_pos,
|
||||
py,
|
||||
&args_array,
|
||||
)?);
|
||||
}
|
||||
|
||||
let (accept_args, accept_kwargs) = accept_args_kwargs(&spec.attrs);
|
||||
|
||||
let cls_name = if let Some(cls) = self_ {
|
||||
quote! { Some(<#cls as pyo3::type_object::PyTypeInfo>::NAME) }
|
||||
} else {
|
||||
quote! { None }
|
||||
};
|
||||
let python_name = &spec.python_name;
|
||||
|
||||
let (args_to_extract, kwargs_to_extract) = if fastcall {
|
||||
// _args is a &[&PyAny], _kwnames is a Option<&PyTuple> containing the
|
||||
// keyword names of the keyword args in _kwargs
|
||||
(
|
||||
// need copied() for &&PyAny -> &PyAny
|
||||
quote! { _args.iter().copied() },
|
||||
quote! { _kwnames.map(|kwnames| {
|
||||
kwnames.as_slice().iter().copied().zip(_kwargs.iter().copied())
|
||||
}) },
|
||||
)
|
||||
} else {
|
||||
// _args is a &PyTuple, _kwargs is an Option<&PyDict>
|
||||
(
|
||||
quote! { _args.iter() },
|
||||
quote! { _kwargs.map(|dict| dict.iter()) },
|
||||
)
|
||||
};
|
||||
|
||||
// create array of arguments, and then parse
|
||||
Ok(quote! {{
|
||||
const DESCRIPTION: pyo3::derive_utils::FunctionDescription = pyo3::derive_utils::FunctionDescription {
|
||||
cls_name: #cls_name,
|
||||
func_name: stringify!(#python_name),
|
||||
positional_parameter_names: &[#(#positional_parameter_names),*],
|
||||
// TODO: https://github.com/PyO3/pyo3/issues/1439 - support specifying these
|
||||
positional_only_parameters: 0,
|
||||
required_positional_parameters: #required_positional_parameters,
|
||||
keyword_only_parameters: &[#(#keyword_only_parameters),*],
|
||||
accept_varargs: #accept_args,
|
||||
accept_varkeywords: #accept_kwargs,
|
||||
};
|
||||
|
||||
let mut #args_array = [None; #num_params];
|
||||
let (_args, _kwargs) = DESCRIPTION.extract_arguments(
|
||||
#py,
|
||||
#args_to_extract,
|
||||
#kwargs_to_extract,
|
||||
&mut #args_array
|
||||
)?;
|
||||
|
||||
#(#param_conversion)*
|
||||
|
||||
#body
|
||||
}})
|
||||
}
|
||||
|
||||
/// 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
|
||||
fn impl_arg_param(
|
||||
arg: &FnArg<'_>,
|
||||
spec: &FnSpec<'_>,
|
||||
idx: usize,
|
||||
self_: Option<&syn::Type>,
|
||||
option_pos: &mut usize,
|
||||
py: &syn::Ident,
|
||||
args_array: &syn::Ident,
|
||||
) -> Result<TokenStream> {
|
||||
// Use this macro inside this function, to ensure that all code generated here is associated
|
||||
// with the function argument
|
||||
macro_rules! quote_arg_span {
|
||||
($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => $($tokens)*) }
|
||||
}
|
||||
|
||||
let arg_name = syn::Ident::new(&format!("arg{}", idx), Span::call_site());
|
||||
|
||||
if arg.py {
|
||||
return Ok(quote_arg_span! { let #arg_name = #py; });
|
||||
}
|
||||
|
||||
let ty = arg.ty;
|
||||
let name = arg.name;
|
||||
let transform_error = quote! {
|
||||
|e| pyo3::derive_utils::argument_extraction_error(#py, stringify!(#name), e)
|
||||
};
|
||||
|
||||
if is_args(&spec.attrs, name) {
|
||||
ensure_spanned!(
|
||||
arg.optional.is_none(),
|
||||
arg.name.span() => "args cannot be optional"
|
||||
);
|
||||
return Ok(quote_arg_span! {
|
||||
let #arg_name = _args.unwrap().extract().map_err(#transform_error)?;
|
||||
});
|
||||
} else if is_kwargs(&spec.attrs, name) {
|
||||
ensure_spanned!(
|
||||
arg.optional.is_some(),
|
||||
arg.name.span() => "kwargs must be Option<_>"
|
||||
);
|
||||
return Ok(quote_arg_span! {
|
||||
let #arg_name = _kwargs.map(|kwargs| kwargs.extract())
|
||||
.transpose()
|
||||
.map_err(#transform_error)?;
|
||||
});
|
||||
}
|
||||
|
||||
let arg_value = quote_arg_span!(#args_array[#option_pos]);
|
||||
*option_pos += 1;
|
||||
|
||||
let extract = if let Some(FromPyWithAttribute(expr_path)) = &arg.attrs.from_py_with {
|
||||
quote_arg_span! { #expr_path(_obj).map_err(#transform_error) }
|
||||
} else {
|
||||
quote_arg_span! { _obj.extract().map_err(#transform_error) }
|
||||
};
|
||||
|
||||
let arg_value_or_default = match (spec.default_value(name), arg.optional.is_some()) {
|
||||
(Some(default), true) if default.to_string() != "None" => {
|
||||
quote_arg_span! { #arg_value.map_or_else(|| Ok(Some(#default)), |_obj| #extract)? }
|
||||
}
|
||||
(Some(default), _) => {
|
||||
quote_arg_span! { #arg_value.map_or_else(|| Ok(#default), |_obj| #extract)? }
|
||||
}
|
||||
(None, true) => quote_arg_span! { #arg_value.map_or(Ok(None), |_obj| #extract)? },
|
||||
(None, false) => {
|
||||
quote_arg_span! {
|
||||
{
|
||||
let _obj = #arg_value.expect("Failed to extract required method argument");
|
||||
#extract?
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return if let syn::Type::Reference(tref) = unwrap_ty_group(arg.optional.unwrap_or(ty)) {
|
||||
let (tref, mut_) = preprocess_tref(tref, self_);
|
||||
let (target_ty, borrow_tmp) = if arg.optional.is_some() {
|
||||
// Get Option<&T> from Option<PyRef<T>>
|
||||
(
|
||||
quote_arg_span! { Option<<#tref as pyo3::derive_utils::ExtractExt<'_>>::Target> },
|
||||
if mut_.is_some() {
|
||||
quote_arg_span! { _tmp.as_deref_mut() }
|
||||
} else {
|
||||
quote_arg_span! { _tmp.as_deref() }
|
||||
},
|
||||
)
|
||||
} else {
|
||||
// Get &T from PyRef<T>
|
||||
(
|
||||
quote_arg_span! { <#tref as pyo3::derive_utils::ExtractExt<'_>>::Target },
|
||||
quote_arg_span! { &#mut_ *_tmp },
|
||||
)
|
||||
};
|
||||
|
||||
Ok(quote_arg_span! {
|
||||
let #mut_ _tmp: #target_ty = #arg_value_or_default;
|
||||
let #arg_name = #borrow_tmp;
|
||||
})
|
||||
} else {
|
||||
Ok(quote_arg_span! {
|
||||
let #arg_name = #arg_value_or_default;
|
||||
})
|
||||
};
|
||||
|
||||
/// Replace `Self`, remove lifetime and get mutability from the type
|
||||
fn preprocess_tref(
|
||||
tref: &syn::TypeReference,
|
||||
self_: Option<&syn::Type>,
|
||||
) -> (syn::TypeReference, Option<syn::token::Mut>) {
|
||||
let mut tref = tref.to_owned();
|
||||
if let Some(syn::Type::Path(tpath)) = self_ {
|
||||
replace_self(&mut tref, &tpath.path);
|
||||
}
|
||||
tref.lifetime = None;
|
||||
let mut_ = tref.mutability;
|
||||
(tref, mut_)
|
||||
}
|
||||
|
||||
/// Replace `Self` with the exact type name since it is used out of the impl block
|
||||
fn replace_self(tref: &mut syn::TypeReference, self_path: &syn::Path) {
|
||||
match &mut *tref.elem {
|
||||
syn::Type::Reference(tref_inner) => replace_self(tref_inner, self_path),
|
||||
syn::Type::Path(tpath) => {
|
||||
if let Some(ident) = tpath.path.get_ident() {
|
||||
if ident == "Self" {
|
||||
tpath.path = self_path.to_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,19 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use crate::method::{FnType, SelfType};
|
||||
use crate::attributes::{
|
||||
self, take_deprecated_text_signature_attribute, take_pyo3_options, NameAttribute,
|
||||
TextSignatureAttribute,
|
||||
};
|
||||
use crate::deprecations::Deprecations;
|
||||
use crate::pyimpl::PyClassMethodsType;
|
||||
use crate::pymethod::{impl_py_getter_def, impl_py_setter_def, PropertyType};
|
||||
use crate::utils;
|
||||
use crate::utils::{self, unwrap_group};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::ext::IdentExt;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{parse_quote, spanned::Spanned, Expr, Token};
|
||||
use syn::{parse_quote, spanned::Spanned, Expr, Result, Token};
|
||||
|
||||
/// The parsed arguments of the pyclass macro
|
||||
pub struct PyClassArgs {
|
||||
|
@ -26,7 +30,7 @@ pub struct PyClassArgs {
|
|||
}
|
||||
|
||||
impl Parse for PyClassArgs {
|
||||
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut slf = PyClassArgs::default();
|
||||
|
||||
let vars = Punctuated::<Expr, Token![,]>::parse_terminated(input)?;
|
||||
|
@ -57,7 +61,7 @@ impl Default for PyClassArgs {
|
|||
impl PyClassArgs {
|
||||
/// Adda single expression from the comma separated list in the attribute, which is
|
||||
/// either a single word or an assignment expression
|
||||
fn add_expr(&mut self, expr: &Expr) -> syn::parse::Result<()> {
|
||||
fn add_expr(&mut self, expr: &Expr) -> Result<()> {
|
||||
match expr {
|
||||
syn::Expr::Path(exp) if exp.path.segments.len() == 1 => self.add_path(exp),
|
||||
syn::Expr::Assign(assign) => self.add_assign(assign),
|
||||
|
@ -89,7 +93,7 @@ impl PyClassArgs {
|
|||
// We allow arbitrary expressions here so you can e.g. use `8*64`
|
||||
self.freelist = Some(syn::Expr::clone(right));
|
||||
}
|
||||
"name" => match &**right {
|
||||
"name" => match unwrap_group(&**right) {
|
||||
syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
|
@ -110,7 +114,7 @@ impl PyClassArgs {
|
|||
}
|
||||
_ => expected!("type name (e.g. \"Name\")"),
|
||||
},
|
||||
"extends" => match &**right {
|
||||
"extends" => match unwrap_group(&**right) {
|
||||
syn::Expr::Path(exp) => {
|
||||
self.base = syn::TypePath {
|
||||
path: exp.path.clone(),
|
||||
|
@ -120,7 +124,7 @@ impl PyClassArgs {
|
|||
}
|
||||
_ => expected!("type path (e.g., my_mod::BaseClass)"),
|
||||
},
|
||||
"module" => match &**right {
|
||||
"module" => match unwrap_group(&**right) {
|
||||
syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
|
@ -162,73 +166,174 @@ impl PyClassArgs {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PyClassPyO3Options {
|
||||
pub text_signature: Option<TextSignatureAttribute>,
|
||||
pub deprecations: Deprecations,
|
||||
}
|
||||
|
||||
enum PyClassPyO3Option {
|
||||
TextSignature(TextSignatureAttribute),
|
||||
}
|
||||
|
||||
impl Parse for PyClassPyO3Option {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::text_signature) {
|
||||
input.parse().map(PyClassPyO3Option::TextSignature)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PyClassPyO3Options {
|
||||
pub fn take_pyo3_options(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
|
||||
let mut options: PyClassPyO3Options = Default::default();
|
||||
for option in take_pyo3_options(attrs)? {
|
||||
match option {
|
||||
PyClassPyO3Option::TextSignature(text_signature) => {
|
||||
options.set_text_signature(text_signature)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
pub fn set_text_signature(
|
||||
&mut self,
|
||||
text_signature: TextSignatureAttribute,
|
||||
) -> syn::Result<()> {
|
||||
ensure_spanned!(
|
||||
self.text_signature.is_none(),
|
||||
text_signature.kw.span() => "`text_signature` may only be specified once"
|
||||
);
|
||||
self.text_signature = Some(text_signature);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_py_class(
|
||||
class: &mut syn::ItemStruct,
|
||||
attr: &PyClassArgs,
|
||||
args: &PyClassArgs,
|
||||
methods_type: PyClassMethodsType,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let text_signature = utils::parse_text_signature_attrs(
|
||||
&mut class.attrs,
|
||||
&get_class_python_name(&class.ident, attr),
|
||||
let mut options = PyClassPyO3Options::take_pyo3_options(&mut class.attrs)?;
|
||||
if let Some(text_signature) =
|
||||
take_deprecated_text_signature_attribute(&mut class.attrs, &mut options.deprecations)?
|
||||
{
|
||||
options.set_text_signature(text_signature)?;
|
||||
}
|
||||
let doc = utils::get_doc(
|
||||
&class.attrs,
|
||||
options
|
||||
.text_signature
|
||||
.as_ref()
|
||||
.map(|attr| (get_class_python_name(&class.ident, args), attr)),
|
||||
)?;
|
||||
let doc = utils::get_doc(&class.attrs, text_signature, true)?;
|
||||
let mut descriptors = Vec::new();
|
||||
|
||||
ensure_spanned!(
|
||||
class.generics.params.is_empty(),
|
||||
class.generics.span() => "#[pyclass] cannot have generic parameters"
|
||||
);
|
||||
|
||||
match &mut class.fields {
|
||||
syn::Fields::Named(fields) => {
|
||||
for field in fields.named.iter_mut() {
|
||||
let field_descs = parse_descriptors(field)?;
|
||||
if !field_descs.is_empty() {
|
||||
descriptors.push((field.clone(), field_descs));
|
||||
}
|
||||
}
|
||||
let field_options = match &mut class.fields {
|
||||
syn::Fields::Named(fields) => fields
|
||||
.named
|
||||
.iter_mut()
|
||||
.map(|field| {
|
||||
FieldPyO3Options::take_pyo3_options(&mut field.attrs)
|
||||
.map(move |options| (&*field, options))
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
syn::Fields::Unnamed(fields) => fields
|
||||
.unnamed
|
||||
.iter_mut()
|
||||
.map(|field| {
|
||||
FieldPyO3Options::take_pyo3_options(&mut field.attrs)
|
||||
.map(move |options| (&*field, options))
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
syn::Fields::Unit => {
|
||||
// No fields for unit struct
|
||||
Vec::new()
|
||||
}
|
||||
syn::Fields::Unnamed(fields) => {
|
||||
for field in fields.unnamed.iter_mut() {
|
||||
let field_descs = parse_descriptors(field)?;
|
||||
if !field_descs.is_empty() {
|
||||
descriptors.push((field.clone(), field_descs));
|
||||
}
|
||||
}
|
||||
}
|
||||
syn::Fields::Unit => { /* No fields for unit struct */ }
|
||||
}
|
||||
};
|
||||
|
||||
impl_class(&class.ident, &attr, doc, descriptors, methods_type)
|
||||
impl_class(
|
||||
&class.ident,
|
||||
args,
|
||||
doc,
|
||||
field_options,
|
||||
methods_type,
|
||||
options.deprecations,
|
||||
)
|
||||
}
|
||||
|
||||
/// Parses `#[pyo3(get, set)]`
|
||||
fn parse_descriptors(item: &mut syn::Field) -> syn::Result<Vec<FnType>> {
|
||||
let mut descs = Vec::new();
|
||||
let mut new_attrs = Vec::new();
|
||||
for attr in item.attrs.drain(..) {
|
||||
if let Ok(syn::Meta::List(list)) = attr.parse_meta() {
|
||||
if list.path.is_ident("pyo3") {
|
||||
for meta in list.nested.iter() {
|
||||
if let syn::NestedMeta::Meta(metaitem) = meta {
|
||||
if metaitem.path().is_ident("get") {
|
||||
descs.push(FnType::Getter(SelfType::Receiver { mutable: false }));
|
||||
} else if metaitem.path().is_ident("set") {
|
||||
descs.push(FnType::Setter(SelfType::Receiver { mutable: true }));
|
||||
} else {
|
||||
bail_spanned!(metaitem.span() => "only get and set are supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new_attrs.push(attr)
|
||||
}
|
||||
/// `#[pyo3()]` options for pyclass fields
|
||||
struct FieldPyO3Options {
|
||||
get: bool,
|
||||
set: bool,
|
||||
name: Option<NameAttribute>,
|
||||
}
|
||||
|
||||
enum FieldPyO3Option {
|
||||
Get(attributes::kw::get),
|
||||
Set(attributes::kw::set),
|
||||
Name(NameAttribute),
|
||||
}
|
||||
|
||||
impl Parse for FieldPyO3Option {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::get) {
|
||||
input.parse().map(FieldPyO3Option::Get)
|
||||
} else if lookahead.peek(attributes::kw::set) {
|
||||
input.parse().map(FieldPyO3Option::Set)
|
||||
} else if lookahead.peek(attributes::kw::name) {
|
||||
input.parse().map(FieldPyO3Option::Name)
|
||||
} else {
|
||||
new_attrs.push(attr);
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
item.attrs = new_attrs;
|
||||
Ok(descs)
|
||||
}
|
||||
|
||||
impl FieldPyO3Options {
|
||||
fn take_pyo3_options(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
|
||||
let mut options = FieldPyO3Options {
|
||||
get: false,
|
||||
set: false,
|
||||
name: None,
|
||||
};
|
||||
|
||||
for option in take_pyo3_options(attrs)? {
|
||||
match option {
|
||||
FieldPyO3Option::Get(kw) => {
|
||||
ensure_spanned!(
|
||||
!options.get,
|
||||
kw.span() => "`get` may only be specified once"
|
||||
);
|
||||
options.get = true;
|
||||
}
|
||||
FieldPyO3Option::Set(kw) => {
|
||||
ensure_spanned!(
|
||||
!options.set,
|
||||
kw.span() => "`set` may only be specified once"
|
||||
);
|
||||
options.set = true;
|
||||
}
|
||||
FieldPyO3Option::Name(name) => {
|
||||
ensure_spanned!(
|
||||
options.name.is_none(),
|
||||
name.0.span() => "`name` may only be specified once"
|
||||
);
|
||||
options.name = Some(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
}
|
||||
|
||||
/// To allow multiple #[pymethods] block, we define inventory types.
|
||||
|
@ -267,46 +372,45 @@ fn impl_class(
|
|||
cls: &syn::Ident,
|
||||
attr: &PyClassArgs,
|
||||
doc: syn::LitStr,
|
||||
descriptors: Vec<(syn::Field, Vec<FnType>)>,
|
||||
field_options: Vec<(&syn::Field, FieldPyO3Options)>,
|
||||
methods_type: PyClassMethodsType,
|
||||
deprecations: Deprecations,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let cls_name = get_class_python_name(cls, attr).to_string();
|
||||
|
||||
let extra = {
|
||||
if let Some(freelist) = &attr.freelist {
|
||||
let alloc = attr.freelist.as_ref().map(|freelist| {
|
||||
quote! {
|
||||
impl pyo3::freelist::PyClassWithFreeList for #cls {
|
||||
impl pyo3::class::impl_::PyClassWithFreeList for #cls {
|
||||
#[inline]
|
||||
fn get_free_list(_py: pyo3::Python) -> &mut pyo3::freelist::FreeList<*mut pyo3::ffi::PyObject> {
|
||||
static mut FREELIST: *mut pyo3::freelist::FreeList<*mut pyo3::ffi::PyObject> = 0 as *mut _;
|
||||
fn get_free_list(_py: pyo3::Python<'_>) -> &mut pyo3::impl_::freelist::FreeList<*mut pyo3::ffi::PyObject> {
|
||||
static mut FREELIST: *mut pyo3::impl_::freelist::FreeList<*mut pyo3::ffi::PyObject> = 0 as *mut _;
|
||||
unsafe {
|
||||
if FREELIST.is_null() {
|
||||
FREELIST = Box::into_raw(Box::new(
|
||||
pyo3::freelist::FreeList::with_capacity(#freelist)));
|
||||
pyo3::impl_::freelist::FreeList::with_capacity(#freelist)));
|
||||
}
|
||||
&mut *FREELIST
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
impl pyo3::pyclass::PyClassAlloc for #cls {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let extra = if !descriptors.is_empty() {
|
||||
let path = syn::Path::from(syn::PathSegment::from(cls.clone()));
|
||||
let ty = syn::Type::from(syn::TypePath { path, qself: None });
|
||||
let desc_impls = impl_descriptors(&ty, descriptors)?;
|
||||
quote! {
|
||||
#desc_impls
|
||||
#extra
|
||||
}
|
||||
} else {
|
||||
extra
|
||||
};
|
||||
impl pyo3::class::impl_::PyClassAllocImpl<#cls> for pyo3::class::impl_::PyClassImplCollector<#cls> {
|
||||
#[inline]
|
||||
fn alloc_impl(self) -> Option<pyo3::ffi::allocfunc> {
|
||||
Some(pyo3::class::impl_::alloc_with_freelist::<#cls>)
|
||||
}
|
||||
}
|
||||
|
||||
impl pyo3::class::impl_::PyClassFreeImpl<#cls> for pyo3::class::impl_::PyClassImplCollector<#cls> {
|
||||
#[inline]
|
||||
fn free_impl(self) -> Option<pyo3::ffi::freefunc> {
|
||||
Some(pyo3::class::impl_::free_with_freelist::<#cls>)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let descriptors = impl_descriptors(cls, field_options)?;
|
||||
|
||||
// insert space for weak ref
|
||||
let weakref = if attr.has_weaklist {
|
||||
|
@ -345,14 +449,14 @@ fn impl_class(
|
|||
quote! {}
|
||||
};
|
||||
|
||||
let (impl_inventory, iter_py_methods) = match methods_type {
|
||||
PyClassMethodsType::Specialization => (None, quote! { collector.py_methods().iter() }),
|
||||
let (impl_inventory, for_each_py_method) = match methods_type {
|
||||
PyClassMethodsType::Specialization => (None, quote! { visitor(collector.py_methods()); }),
|
||||
PyClassMethodsType::Inventory => (
|
||||
Some(impl_methods_inventory(&cls)),
|
||||
Some(impl_methods_inventory(cls)),
|
||||
quote! {
|
||||
pyo3::inventory::iter::<<Self as pyo3::class::impl_::HasMethodsInventory>::Methods>
|
||||
.into_iter()
|
||||
.flat_map(pyo3::class::impl_::PyMethodsInventory::get)
|
||||
for inventory in pyo3::inventory::iter::<<Self as pyo3::class::impl_::HasMethodsInventory>::Methods>() {
|
||||
visitor(pyo3::class::impl_::PyMethodsInventory::get(inventory));
|
||||
}
|
||||
},
|
||||
),
|
||||
};
|
||||
|
@ -399,7 +503,9 @@ fn impl_class(
|
|||
const MODULE: Option<&'static str> = #module;
|
||||
|
||||
#[inline]
|
||||
fn type_object_raw(py: pyo3::Python) -> *mut pyo3::ffi::PyTypeObject {
|
||||
fn type_object_raw(py: pyo3::Python<'_>) -> *mut pyo3::ffi::PyTypeObject {
|
||||
#deprecations
|
||||
|
||||
use pyo3::type_object::LazyStaticType;
|
||||
static TYPE_OBJECT: LazyStaticType = LazyStaticType::new();
|
||||
TYPE_OBJECT.get_or_init::<Self>(py)
|
||||
|
@ -432,49 +538,56 @@ fn impl_class(
|
|||
const IS_BASETYPE: bool = #is_basetype;
|
||||
const IS_SUBCLASS: bool = #is_subclass;
|
||||
|
||||
type Layout = PyCell<Self>;
|
||||
type Layout = pyo3::PyCell<Self>;
|
||||
type BaseType = #base;
|
||||
type ThreadChecker = #thread_checker;
|
||||
|
||||
fn for_each_method_def(visitor: &mut dyn FnMut(&pyo3::class::PyMethodDefType)) {
|
||||
fn for_each_method_def(visitor: &mut dyn FnMut(&[pyo3::class::PyMethodDefType])) {
|
||||
use pyo3::class::impl_::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
#iter_py_methods
|
||||
.chain(collector.py_class_descriptors())
|
||||
.chain(collector.object_protocol_methods())
|
||||
.chain(collector.async_protocol_methods())
|
||||
.chain(collector.context_protocol_methods())
|
||||
.chain(collector.descr_protocol_methods())
|
||||
.chain(collector.mapping_protocol_methods())
|
||||
.chain(collector.number_protocol_methods())
|
||||
.for_each(visitor)
|
||||
#for_each_py_method;
|
||||
visitor(collector.py_class_descriptors());
|
||||
visitor(collector.object_protocol_methods());
|
||||
visitor(collector.async_protocol_methods());
|
||||
visitor(collector.context_protocol_methods());
|
||||
visitor(collector.descr_protocol_methods());
|
||||
visitor(collector.mapping_protocol_methods());
|
||||
visitor(collector.number_protocol_methods());
|
||||
}
|
||||
fn get_new() -> Option<pyo3::ffi::newfunc> {
|
||||
use pyo3::class::impl_::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.new_impl()
|
||||
}
|
||||
fn get_alloc() -> Option<pyo3::ffi::allocfunc> {
|
||||
use pyo3::class::impl_::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.alloc_impl()
|
||||
}
|
||||
fn get_free() -> Option<pyo3::ffi::freefunc> {
|
||||
use pyo3::class::impl_::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.free_impl()
|
||||
}
|
||||
fn get_call() -> Option<pyo3::ffi::PyCFunctionWithKeywords> {
|
||||
use pyo3::class::impl_::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.call_impl()
|
||||
}
|
||||
|
||||
fn for_each_proto_slot(visitor: &mut dyn FnMut(&pyo3::ffi::PyType_Slot)) {
|
||||
fn for_each_proto_slot(visitor: &mut dyn FnMut(&[pyo3::ffi::PyType_Slot])) {
|
||||
// Implementation which uses dtolnay specialization to load all slots.
|
||||
use pyo3::class::impl_::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.object_protocol_slots()
|
||||
.iter()
|
||||
.chain(collector.number_protocol_slots())
|
||||
.chain(collector.iter_protocol_slots())
|
||||
.chain(collector.gc_protocol_slots())
|
||||
.chain(collector.descr_protocol_slots())
|
||||
.chain(collector.mapping_protocol_slots())
|
||||
.chain(collector.sequence_protocol_slots())
|
||||
.chain(collector.async_protocol_slots())
|
||||
.chain(collector.buffer_protocol_slots())
|
||||
.for_each(visitor);
|
||||
visitor(collector.object_protocol_slots());
|
||||
visitor(collector.number_protocol_slots());
|
||||
visitor(collector.iter_protocol_slots());
|
||||
visitor(collector.gc_protocol_slots());
|
||||
visitor(collector.descr_protocol_slots());
|
||||
visitor(collector.mapping_protocol_slots());
|
||||
visitor(collector.sequence_protocol_slots());
|
||||
visitor(collector.async_protocol_slots());
|
||||
visitor(collector.buffer_protocol_slots());
|
||||
}
|
||||
|
||||
fn get_buffer() -> Option<&'static pyo3::class::impl_::PyBufferProcs> {
|
||||
|
@ -484,39 +597,50 @@ fn impl_class(
|
|||
}
|
||||
}
|
||||
|
||||
#extra
|
||||
#alloc
|
||||
|
||||
#descriptors
|
||||
|
||||
#gc_impl
|
||||
})
|
||||
}
|
||||
|
||||
fn impl_descriptors(
|
||||
cls: &syn::Type,
|
||||
descriptors: Vec<(syn::Field, Vec<FnType>)>,
|
||||
cls: &syn::Ident,
|
||||
field_options: Vec<(&syn::Field, FieldPyO3Options)>,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let py_methods: Vec<TokenStream> = descriptors
|
||||
.iter()
|
||||
.flat_map(|(field, fns)| {
|
||||
fns.iter()
|
||||
.map(|desc| {
|
||||
let doc = utils::get_doc(&field.attrs, None, true)
|
||||
.unwrap_or_else(|_| syn::LitStr::new("", Span::call_site()));
|
||||
let property_type = PropertyType::Descriptor(
|
||||
field.ident.as_ref().ok_or_else(
|
||||
|| err_spanned!(field.span() => "`#[pyo3(get, set)]` is not supported on tuple struct fields")
|
||||
)?
|
||||
);
|
||||
match desc {
|
||||
FnType::Getter(self_ty) => {
|
||||
impl_py_getter_def(cls, property_type, self_ty, &doc, &Default::default())
|
||||
}
|
||||
FnType::Setter(self_ty) => {
|
||||
impl_py_setter_def(cls, property_type, self_ty, &doc, &Default::default())
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<syn::Result<TokenStream>>>()
|
||||
let ty = syn::parse_quote!(#cls);
|
||||
let py_methods: Vec<TokenStream> = field_options
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.flat_map(|(field_index, (field, options))| {
|
||||
let name_err = if options.name.is_some() && !options.get && !options.set {
|
||||
Some(Err(err_spanned!(options.name.as_ref().unwrap().0.span() => "`name` is useless without `get` or `set`")))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let getter = if options.get {
|
||||
Some(impl_py_getter_def(&ty, PropertyType::Descriptor {
|
||||
field_index,
|
||||
field,
|
||||
python_name: options.name.as_ref()
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let setter = if options.set {
|
||||
Some(impl_py_setter_def(&ty, PropertyType::Descriptor {
|
||||
field_index,
|
||||
field,
|
||||
python_name: options.name.as_ref()
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
name_err.into_iter().chain(getter).chain(setter)
|
||||
})
|
||||
.collect::<syn::Result<_>>()?;
|
||||
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
use crate::{
|
||||
attributes::{
|
||||
self, get_deprecated_name_attribute, get_pyo3_attributes, take_attributes,
|
||||
FromPyWithAttribute, NameAttribute,
|
||||
self, get_deprecated_name_attribute, get_deprecated_text_signature_attribute,
|
||||
get_pyo3_options, take_attributes, FromPyWithAttribute, NameAttribute,
|
||||
TextSignatureAttribute,
|
||||
},
|
||||
deprecations::Deprecations,
|
||||
method::{self, FnArg, FnSpec},
|
||||
pymethod::{check_generic, get_arg_names, impl_arg_params},
|
||||
method::{self, CallingConvention, FnArg},
|
||||
pymethod::check_generic,
|
||||
utils::{self, ensure_not_async_fn},
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
@ -62,7 +63,7 @@ impl PyFunctionArgPyO3Attributes {
|
|||
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
|
||||
let mut attributes = PyFunctionArgPyO3Attributes { from_py_with: None };
|
||||
take_attributes(attrs, |attr| {
|
||||
if let Some(pyo3_attrs) = get_pyo3_attributes(attr)? {
|
||||
if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
|
||||
for attr in pyo3_attrs {
|
||||
match attr {
|
||||
PyFunctionArgPyO3Attribute::FromPyWith(from_py_with) => {
|
||||
|
@ -208,18 +209,20 @@ impl PyFunctionSignature {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct PyFunctionOptions {
|
||||
pub pass_module: bool,
|
||||
pub pass_module: Option<attributes::kw::pass_module>,
|
||||
pub name: Option<NameAttribute>,
|
||||
pub signature: Option<PyFunctionSignature>,
|
||||
pub text_signature: Option<TextSignatureAttribute>,
|
||||
pub deprecations: Deprecations,
|
||||
}
|
||||
|
||||
impl Parse for PyFunctionOptions {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut options = PyFunctionOptions {
|
||||
pass_module: false,
|
||||
pass_module: None,
|
||||
name: None,
|
||||
signature: None,
|
||||
text_signature: None,
|
||||
deprecations: Deprecations::new(),
|
||||
};
|
||||
|
||||
|
@ -228,6 +231,7 @@ impl Parse for PyFunctionOptions {
|
|||
if lookahead.peek(attributes::kw::name)
|
||||
|| lookahead.peek(attributes::kw::pass_module)
|
||||
|| lookahead.peek(attributes::kw::signature)
|
||||
|| lookahead.peek(attributes::kw::text_signature)
|
||||
{
|
||||
options.add_attributes(std::iter::once(input.parse()?))?;
|
||||
if !input.is_empty() {
|
||||
|
@ -250,6 +254,7 @@ pub enum PyFunctionOption {
|
|||
Name(NameAttribute),
|
||||
PassModule(attributes::kw::pass_module),
|
||||
Signature(PyFunctionSignature),
|
||||
TextSignature(TextSignatureAttribute),
|
||||
}
|
||||
|
||||
impl Parse for PyFunctionOption {
|
||||
|
@ -261,6 +266,8 @@ impl Parse for PyFunctionOption {
|
|||
input.parse().map(PyFunctionOption::PassModule)
|
||||
} else if lookahead.peek(attributes::kw::signature) {
|
||||
input.parse().map(PyFunctionOption::Signature)
|
||||
} else if lookahead.peek(attributes::kw::text_signature) {
|
||||
input.parse().map(PyFunctionOption::TextSignature)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
|
@ -270,19 +277,26 @@ impl Parse for PyFunctionOption {
|
|||
impl PyFunctionOptions {
|
||||
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
|
||||
let mut options = PyFunctionOptions::default();
|
||||
options.take_pyo3_attributes(attrs)?;
|
||||
options.take_pyo3_options(attrs)?;
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
pub fn take_pyo3_attributes(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
|
||||
pub fn take_pyo3_options(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
|
||||
take_attributes(attrs, |attr| {
|
||||
if let Some(pyo3_attributes) = get_pyo3_attributes(attr)? {
|
||||
if let Some(pyo3_attributes) = get_pyo3_options(attr)? {
|
||||
self.add_attributes(pyo3_attributes)?;
|
||||
Ok(true)
|
||||
} else if let Some(name) = get_deprecated_name_attribute(attr, &mut self.deprecations)?
|
||||
{
|
||||
self.set_name(name)?;
|
||||
Ok(true)
|
||||
} else if let Some(text_signature) =
|
||||
get_deprecated_text_signature_attribute(attr, &mut self.deprecations)?
|
||||
{
|
||||
self.add_attributes(std::iter::once(PyFunctionOption::TextSignature(
|
||||
text_signature,
|
||||
)))?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
|
@ -300,10 +314,10 @@ impl PyFunctionOptions {
|
|||
PyFunctionOption::Name(name) => self.set_name(name)?,
|
||||
PyFunctionOption::PassModule(kw) => {
|
||||
ensure_spanned!(
|
||||
!self.pass_module,
|
||||
self.pass_module.is_none(),
|
||||
kw.span() => "`pass_module` may only be specified once"
|
||||
);
|
||||
self.pass_module = true;
|
||||
self.pass_module = Some(kw);
|
||||
}
|
||||
PyFunctionOption::Signature(signature) => {
|
||||
ensure_spanned!(
|
||||
|
@ -313,6 +327,13 @@ impl PyFunctionOptions {
|
|||
);
|
||||
self.signature = Some(signature);
|
||||
}
|
||||
PyFunctionOption::TextSignature(text_signature) => {
|
||||
ensure_spanned!(
|
||||
self.text_signature.is_none(),
|
||||
text_signature.kw.span() => "`text_signature` may only be specified once"
|
||||
);
|
||||
self.text_signature = Some(text_signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -332,7 +353,7 @@ pub fn build_py_function(
|
|||
ast: &mut syn::ItemFn,
|
||||
mut options: PyFunctionOptions,
|
||||
) -> syn::Result<TokenStream> {
|
||||
options.take_pyo3_attributes(&mut ast.attrs)?;
|
||||
options.take_pyo3_options(&mut ast.attrs)?;
|
||||
Ok(impl_wrap_pyfunction(ast, options)?.1)
|
||||
}
|
||||
|
||||
|
@ -364,7 +385,7 @@ pub fn impl_wrap_pyfunction(
|
|||
.map(FnArg::parse)
|
||||
.collect::<syn::Result<Vec<_>>>()?;
|
||||
|
||||
if options.pass_module {
|
||||
if options.pass_module.is_some() {
|
||||
const PASS_MODULE_ERR: &str = "expected &PyModule as first argument with `pass_module`";
|
||||
ensure_spanned!(
|
||||
!arguments.is_empty(),
|
||||
|
@ -379,14 +400,24 @@ pub fn impl_wrap_pyfunction(
|
|||
|
||||
let ty = method::get_return_info(&func.sig.output);
|
||||
|
||||
let text_signature = utils::parse_text_signature_attrs(&mut func.attrs, &python_name)?;
|
||||
let doc = utils::get_doc(&func.attrs, text_signature, true)?;
|
||||
let doc = utils::get_doc(
|
||||
&func.attrs,
|
||||
options
|
||||
.text_signature
|
||||
.as_ref()
|
||||
.map(|attr| (&python_name, attr)),
|
||||
)?;
|
||||
|
||||
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
|
||||
|
||||
let spec = method::FnSpec {
|
||||
tp: method::FnType::FnStatic,
|
||||
name: &function_wrapper_ident,
|
||||
tp: if options.pass_module.is_some() {
|
||||
method::FnType::FnModule
|
||||
} else {
|
||||
method::FnType::FnStatic
|
||||
},
|
||||
name: &func.sig.ident,
|
||||
convention: CallingConvention::from_args(&arguments, &signature.arguments),
|
||||
python_name,
|
||||
attrs: signature.arguments,
|
||||
args: arguments,
|
||||
|
@ -395,101 +426,21 @@ pub fn impl_wrap_pyfunction(
|
|||
deprecations: options.deprecations,
|
||||
};
|
||||
|
||||
let doc = &spec.doc;
|
||||
let python_name = spec.null_terminated_python_name();
|
||||
let wrapper_ident = format_ident!("__pyo3_raw_{}", spec.name);
|
||||
let wrapper = spec.get_wrapper_function(&wrapper_ident, None)?;
|
||||
let methoddef = spec.get_methoddef(wrapper_ident);
|
||||
|
||||
let name = &func.sig.ident;
|
||||
let wrapper_ident = format_ident!("__pyo3_raw_{}", name);
|
||||
let wrapper = function_c_wrapper(name, &wrapper_ident, &spec, options.pass_module)?;
|
||||
let methoddef = if spec.args.is_empty() {
|
||||
quote!(noargs)
|
||||
} else {
|
||||
quote!(cfunction_with_keywords)
|
||||
};
|
||||
let cfunc = if spec.args.is_empty() {
|
||||
quote!(PyCFunction)
|
||||
} else {
|
||||
quote!(PyCFunctionWithKeywords)
|
||||
};
|
||||
let wrapped_pyfunction = quote! {
|
||||
#wrapper
|
||||
pub(crate) fn #function_wrapper_ident<'a>(
|
||||
args: impl Into<pyo3::derive_utils::PyFunctionArguments<'a>>
|
||||
) -> pyo3::PyResult<&'a pyo3::types::PyCFunction> {
|
||||
pyo3::types::PyCFunction::internal_new(
|
||||
pyo3::class::methods::PyMethodDef:: #methoddef (
|
||||
#python_name,
|
||||
pyo3::class::methods:: #cfunc (#wrapper_ident),
|
||||
#doc,
|
||||
),
|
||||
args.into(),
|
||||
)
|
||||
pyo3::types::PyCFunction::internal_new(#methoddef, args.into())
|
||||
}
|
||||
};
|
||||
Ok((function_wrapper_ident, wrapped_pyfunction))
|
||||
}
|
||||
|
||||
/// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords)
|
||||
fn function_c_wrapper(
|
||||
name: &Ident,
|
||||
wrapper_ident: &Ident,
|
||||
spec: &FnSpec<'_>,
|
||||
pass_module: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let names: Vec<Ident> = get_arg_names(&spec);
|
||||
let (cb, slf_module) = if pass_module {
|
||||
(
|
||||
quote! {
|
||||
pyo3::callback::convert(_py, #name(_slf, #(#names),*))
|
||||
},
|
||||
Some(quote! {
|
||||
let _slf = _py.from_borrowed_ptr::<pyo3::types::PyModule>(_slf);
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
quote! {
|
||||
pyo3::callback::convert(_py, #name(#(#names),*))
|
||||
},
|
||||
None,
|
||||
)
|
||||
};
|
||||
let py = syn::Ident::new("_py", Span::call_site());
|
||||
let deprecations = &spec.deprecations;
|
||||
if spec.args.is_empty() {
|
||||
Ok(quote! {
|
||||
unsafe extern "C" fn #wrapper_ident(
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_unused: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
#deprecations
|
||||
pyo3::callback::handle_panic(|#py| {
|
||||
#slf_module
|
||||
#cb
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
let body = impl_arg_params(spec, None, cb, &py)?;
|
||||
Ok(quote! {
|
||||
unsafe extern "C" fn #wrapper_ident(
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_args: *mut pyo3::ffi::PyObject,
|
||||
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
#deprecations
|
||||
pyo3::callback::handle_panic(|#py| {
|
||||
#slf_module
|
||||
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
|
||||
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
|
||||
|
||||
#body
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn type_is_pymodule(ty: &syn::Type) -> bool {
|
||||
if let syn::Type::Reference(tyref) = ty {
|
||||
if let syn::Type::Path(typath) = tyref.elem.as_ref() {
|
||||
|
@ -508,7 +459,7 @@ fn type_is_pymodule(ty: &syn::Type) -> bool {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use super::{Argument, PyFunctionSignature};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
|
|
@ -67,7 +67,7 @@ pub fn impl_methods(
|
|||
attributes,
|
||||
};
|
||||
let attrs = get_cfg_attributes(&konst.attrs);
|
||||
let meth = pymethod::gen_py_const(ty, &spec);
|
||||
let meth = gen_py_const(ty, &spec);
|
||||
methods.push(quote!(#(#attrs)* #meth));
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +89,26 @@ pub fn impl_methods(
|
|||
})
|
||||
}
|
||||
|
||||
fn gen_py_const(cls: &syn::Type, spec: &ConstSpec) -> TokenStream {
|
||||
let member = &spec.rust_ident;
|
||||
let deprecations = &spec.attributes.deprecations;
|
||||
let python_name = &spec.null_terminated_python_name();
|
||||
quote! {
|
||||
pyo3::class::PyMethodDefType::ClassAttribute({
|
||||
pyo3::class::PyClassAttributeDef::new(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyClassAttributeFactory({
|
||||
fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject {
|
||||
#deprecations
|
||||
pyo3::IntoPy::into_py(#cls::#member, py)
|
||||
}
|
||||
__wrap
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_py_methods(ty: &syn::Type, methods: Vec<TokenStream>) -> TokenStream {
|
||||
quote! {
|
||||
impl pyo3::class::impl_::PyMethods<#ty>
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
use crate::utils::ensure_not_async_fn;
|
||||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
use crate::{attributes::FromPyWithAttribute, konst::ConstSpec};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::attributes::NameAttribute;
|
||||
use crate::utils::ensure_not_async_fn;
|
||||
use crate::{deprecations::Deprecations, utils};
|
||||
use crate::{
|
||||
method::{FnArg, FnSpec, FnType, SelfType},
|
||||
pyfunction::PyFunctionOptions,
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
use quote::quote;
|
||||
use syn::{ext::IdentExt, spanned::Spanned, Result};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum PropertyType<'a> {
|
||||
Descriptor(&'a syn::Ident),
|
||||
Function(&'a FnSpec<'a>),
|
||||
}
|
||||
|
||||
pub enum GeneratedPyMethod {
|
||||
Method(TokenStream),
|
||||
New(TokenStream),
|
||||
|
@ -30,39 +27,47 @@ pub fn gen_py_method(
|
|||
) -> Result<GeneratedPyMethod> {
|
||||
check_generic(sig)?;
|
||||
ensure_not_async_fn(sig)?;
|
||||
ensure_function_options_valid(&options)?;
|
||||
let spec = FnSpec::parse(sig, &mut *meth_attrs, options)?;
|
||||
|
||||
Ok(match &spec.tp {
|
||||
FnType::Fn(self_ty) => {
|
||||
GeneratedPyMethod::Method(impl_py_method_def(cls, &spec, self_ty, None)?)
|
||||
}
|
||||
// ordinary functions (with some specialties)
|
||||
FnType::Fn(_) => GeneratedPyMethod::Method(impl_py_method_def(cls, &spec, None)?),
|
||||
FnType::FnClass => GeneratedPyMethod::Method(impl_py_method_def(
|
||||
cls,
|
||||
&spec,
|
||||
Some(quote!(pyo3::ffi::METH_CLASS)),
|
||||
)?),
|
||||
FnType::FnStatic => GeneratedPyMethod::Method(impl_py_method_def(
|
||||
cls,
|
||||
&spec,
|
||||
Some(quote!(pyo3::ffi::METH_STATIC)),
|
||||
)?),
|
||||
// special prototypes
|
||||
FnType::FnNew => GeneratedPyMethod::New(impl_py_method_def_new(cls, &spec)?),
|
||||
FnType::FnCall(self_ty) => {
|
||||
GeneratedPyMethod::Call(impl_py_method_def_call(cls, &spec, self_ty)?)
|
||||
}
|
||||
FnType::FnClass => GeneratedPyMethod::Method(impl_py_method_def_class(cls, &spec)?),
|
||||
FnType::FnStatic => GeneratedPyMethod::Method(impl_py_method_def_static(cls, &spec)?),
|
||||
FnType::ClassAttribute => {
|
||||
GeneratedPyMethod::Method(impl_py_method_class_attribute(cls, &spec))
|
||||
}
|
||||
FnType::Getter(self_ty) => GeneratedPyMethod::Method(impl_py_getter_def(
|
||||
FnType::FnCall(_) => GeneratedPyMethod::Call(impl_py_method_def_call(cls, &spec)?),
|
||||
FnType::ClassAttribute => GeneratedPyMethod::Method(impl_py_class_attribute(cls, &spec)),
|
||||
FnType::Getter(self_type) => GeneratedPyMethod::Method(impl_py_getter_def(
|
||||
cls,
|
||||
PropertyType::Function(&spec),
|
||||
self_ty,
|
||||
&spec.doc,
|
||||
&spec.deprecations,
|
||||
PropertyType::Function {
|
||||
self_type,
|
||||
spec: &spec,
|
||||
},
|
||||
)?),
|
||||
FnType::Setter(self_ty) => GeneratedPyMethod::Method(impl_py_setter_def(
|
||||
FnType::Setter(self_type) => GeneratedPyMethod::Method(impl_py_setter_def(
|
||||
cls,
|
||||
PropertyType::Function(&spec),
|
||||
self_ty,
|
||||
&spec.doc,
|
||||
&spec.deprecations,
|
||||
PropertyType::Function {
|
||||
self_type,
|
||||
spec: &spec,
|
||||
},
|
||||
)?),
|
||||
FnType::FnModule => {
|
||||
unreachable!("methods cannot be FnModule")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn check_generic(sig: &syn::Signature) -> syn::Result<()> {
|
||||
pub fn check_generic(sig: &syn::Signature) -> syn::Result<()> {
|
||||
let err_msg = |typ| format!("Python functions cannot have generic {} parameters", typ);
|
||||
for param in &sig.generics.params {
|
||||
match param {
|
||||
|
@ -74,214 +79,81 @@ pub(crate) fn check_generic(sig: &syn::Signature) -> syn::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec) -> TokenStream {
|
||||
let member = &spec.rust_ident;
|
||||
let deprecations = &spec.attributes.deprecations;
|
||||
let wrapper = quote! {{
|
||||
fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject {
|
||||
#deprecations
|
||||
pyo3::IntoPy::into_py(#cls::#member, py)
|
||||
}
|
||||
__wrap
|
||||
}};
|
||||
impl_py_const_class_attribute(&spec, &wrapper)
|
||||
fn ensure_function_options_valid(options: &PyFunctionOptions) -> syn::Result<()> {
|
||||
if let Some(pass_module) = &options.pass_module {
|
||||
bail_spanned!(pass_module.span() => "`pass_module` cannot be used on Python methods")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate function wrapper for PyCFunctionWithKeywords
|
||||
pub fn impl_wrap_cfunction_with_keywords(
|
||||
/// Also used by pyfunction.
|
||||
pub fn impl_py_method_def(
|
||||
cls: &syn::Type,
|
||||
spec: &FnSpec<'_>,
|
||||
self_ty: &SelfType,
|
||||
spec: &FnSpec,
|
||||
flags: Option<TokenStream>,
|
||||
) -> Result<TokenStream> {
|
||||
let body = impl_call(cls, &spec);
|
||||
let slf = self_ty.receiver(cls);
|
||||
let py = syn::Ident::new("_py", Span::call_site());
|
||||
let body = impl_arg_params(&spec, Some(cls), body, &py)?;
|
||||
let deprecations = &spec.deprecations;
|
||||
Ok(quote! {{
|
||||
unsafe extern "C" fn __wrap(
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_args: *mut pyo3::ffi::PyObject,
|
||||
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
#deprecations
|
||||
pyo3::callback::handle_panic(|#py| {
|
||||
#slf
|
||||
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
|
||||
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
|
||||
|
||||
#body
|
||||
})
|
||||
}
|
||||
__wrap
|
||||
}})
|
||||
}
|
||||
|
||||
/// Generate function wrapper PyCFunction
|
||||
pub fn impl_wrap_noargs(cls: &syn::Type, spec: &FnSpec<'_>, self_ty: &SelfType) -> TokenStream {
|
||||
let body = impl_call(cls, &spec);
|
||||
let slf = self_ty.receiver(cls);
|
||||
let deprecations = &spec.deprecations;
|
||||
assert!(spec.args.is_empty());
|
||||
quote! {{
|
||||
unsafe extern "C" fn __wrap(
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_args: *mut pyo3::ffi::PyObject,
|
||||
) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
#deprecations
|
||||
pyo3::callback::handle_panic(|_py| {
|
||||
#slf
|
||||
#body
|
||||
})
|
||||
}
|
||||
__wrap
|
||||
}}
|
||||
}
|
||||
|
||||
/// Generate class method wrapper (PyCFunction, PyCFunctionWithKeywords)
|
||||
pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStream> {
|
||||
let name = &spec.name;
|
||||
let names: Vec<syn::Ident> = get_arg_names(&spec);
|
||||
let cb = quote! { #cls::#name(#(#names),*) };
|
||||
let py = syn::Ident::new("_py", Span::call_site());
|
||||
let body = impl_arg_params(spec, Some(cls), cb, &py)?;
|
||||
let deprecations = &spec.deprecations;
|
||||
Ok(quote! {{
|
||||
#[allow(unused_mut)]
|
||||
unsafe extern "C" fn __wrap(
|
||||
subtype: *mut pyo3::ffi::PyTypeObject,
|
||||
_args: *mut pyo3::ffi::PyObject,
|
||||
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
#deprecations
|
||||
use pyo3::callback::IntoPyCallbackOutput;
|
||||
pyo3::callback::handle_panic(|#py| {
|
||||
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
|
||||
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
|
||||
|
||||
let initializer: pyo3::PyClassInitializer::<#cls> = #body.convert(#py)?;
|
||||
let cell = initializer.create_cell_from_subtype(#py, subtype)?;
|
||||
Ok(cell as *mut pyo3::ffi::PyObject)
|
||||
})
|
||||
}
|
||||
__wrap
|
||||
}})
|
||||
}
|
||||
|
||||
/// Generate class method wrapper (PyCFunction, PyCFunctionWithKeywords)
|
||||
pub fn impl_wrap_class(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStream> {
|
||||
let name = &spec.name;
|
||||
let names: Vec<syn::Ident> = get_arg_names(&spec);
|
||||
let cb = quote! { pyo3::callback::convert(_py, #cls::#name(&_cls, #(#names),*)) };
|
||||
let py = syn::Ident::new("_py", Span::call_site());
|
||||
let body = impl_arg_params(spec, Some(cls), cb, &py)?;
|
||||
let deprecations = &spec.deprecations;
|
||||
Ok(quote! {{
|
||||
#[allow(unused_mut)]
|
||||
unsafe extern "C" fn __wrap(
|
||||
_cls: *mut pyo3::ffi::PyObject,
|
||||
_args: *mut pyo3::ffi::PyObject,
|
||||
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
#deprecations
|
||||
pyo3::callback::handle_panic(|#py| {
|
||||
let _cls = pyo3::types::PyType::from_type_ptr(#py, _cls as *mut pyo3::ffi::PyTypeObject);
|
||||
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
|
||||
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
|
||||
|
||||
#body
|
||||
})
|
||||
}
|
||||
__wrap
|
||||
}})
|
||||
}
|
||||
|
||||
/// Generate static method wrapper (PyCFunction, PyCFunctionWithKeywords)
|
||||
pub fn impl_wrap_static(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStream> {
|
||||
let name = &spec.name;
|
||||
let names: Vec<syn::Ident> = get_arg_names(&spec);
|
||||
let cb = quote! { pyo3::callback::convert(_py, #cls::#name(#(#names),*)) };
|
||||
let py = syn::Ident::new("_py", Span::call_site());
|
||||
let body = impl_arg_params(spec, Some(cls), cb, &py)?;
|
||||
let deprecations = &spec.deprecations;
|
||||
Ok(quote! {{
|
||||
#[allow(unused_mut)]
|
||||
unsafe extern "C" fn __wrap(
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_args: *mut pyo3::ffi::PyObject,
|
||||
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
#deprecations
|
||||
pyo3::callback::handle_panic(|#py| {
|
||||
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
|
||||
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
|
||||
|
||||
#body
|
||||
})
|
||||
}
|
||||
__wrap
|
||||
}})
|
||||
}
|
||||
|
||||
/// Generate a wrapper for initialization of a class attribute from a method
|
||||
/// annotated with `#[classattr]`.
|
||||
/// To be called in `pyo3::pyclass::initialize_type_object`.
|
||||
pub fn impl_wrap_class_attribute(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
|
||||
let name = &spec.name;
|
||||
let cb = quote! { #cls::#name() };
|
||||
let deprecations = &spec.deprecations;
|
||||
quote! {{
|
||||
fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject {
|
||||
#deprecations
|
||||
pyo3::IntoPy::into_py(#cb, py)
|
||||
}
|
||||
__wrap
|
||||
}}
|
||||
}
|
||||
|
||||
fn impl_call_getter(cls: &syn::Type, spec: &FnSpec) -> syn::Result<TokenStream> {
|
||||
let (py_arg, args) = split_off_python_arg(&spec.args);
|
||||
ensure_spanned!(
|
||||
args.is_empty(),
|
||||
args[0].ty.span() => "getter function can only have one argument (of type pyo3::Python)"
|
||||
);
|
||||
|
||||
let name = &spec.name;
|
||||
let fncall = if py_arg.is_some() {
|
||||
quote!(#cls::#name(_slf, _py))
|
||||
} else {
|
||||
quote!(#cls::#name(_slf))
|
||||
let wrapper_ident = syn::Ident::new("__wrap", Span::call_site());
|
||||
let wrapper_def = spec.get_wrapper_function(&wrapper_ident, Some(cls))?;
|
||||
let add_flags = flags.map(|flags| quote!(.flags(#flags)));
|
||||
let methoddef_type = match spec.tp {
|
||||
FnType::FnStatic => quote!(Static),
|
||||
FnType::FnClass => quote!(Class),
|
||||
_ => quote!(Method),
|
||||
};
|
||||
|
||||
Ok(fncall)
|
||||
let methoddef = spec.get_methoddef(quote! {{ #wrapper_def #wrapper_ident }});
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::#methoddef_type(#methoddef #add_flags)
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate a function wrapper called `__wrap` for a property getter
|
||||
pub(crate) fn impl_wrap_getter(
|
||||
cls: &syn::Type,
|
||||
property_type: PropertyType,
|
||||
self_ty: &SelfType,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let getter_impl = match &property_type {
|
||||
PropertyType::Descriptor(ident) => {
|
||||
quote!(_slf.#ident.clone())
|
||||
fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
|
||||
let wrapper_ident = syn::Ident::new("__wrap", Span::call_site());
|
||||
let wrapper = spec.get_wrapper_function(&wrapper_ident, Some(cls))?;
|
||||
Ok(quote! {
|
||||
impl pyo3::class::impl_::PyClassNewImpl<#cls> for pyo3::class::impl_::PyClassImplCollector<#cls> {
|
||||
fn new_impl(self) -> Option<pyo3::ffi::newfunc> {
|
||||
Some({
|
||||
#wrapper
|
||||
#wrapper_ident
|
||||
})
|
||||
}
|
||||
}
|
||||
PropertyType::Function(spec) => impl_call_getter(cls, spec)?,
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
let slf = self_ty.receiver(cls);
|
||||
Ok(quote! {{
|
||||
unsafe extern "C" fn __wrap(
|
||||
_slf: *mut pyo3::ffi::PyObject, _: *mut std::os::raw::c_void) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
pyo3::callback::handle_panic(|_py| {
|
||||
#slf
|
||||
pyo3::callback::convert(_py, #getter_impl)
|
||||
})
|
||||
fn impl_py_method_def_call(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
|
||||
let wrapper_ident = syn::Ident::new("__wrap", Span::call_site());
|
||||
let wrapper = spec.get_wrapper_function(&wrapper_ident, Some(cls))?;
|
||||
Ok(quote! {
|
||||
impl pyo3::class::impl_::PyClassCallImpl<#cls> for pyo3::class::impl_::PyClassImplCollector<#cls> {
|
||||
fn call_impl(self) -> Option<pyo3::ffi::PyCFunctionWithKeywords> {
|
||||
Some({
|
||||
#wrapper
|
||||
#wrapper_ident
|
||||
})
|
||||
}
|
||||
}
|
||||
__wrap
|
||||
}})
|
||||
})
|
||||
}
|
||||
|
||||
fn impl_py_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenStream {
|
||||
let name = &spec.name;
|
||||
let deprecations = &spec.deprecations;
|
||||
let python_name = spec.null_terminated_python_name();
|
||||
quote! {
|
||||
pyo3::class::PyMethodDefType::ClassAttribute({
|
||||
pyo3::class::PyClassAttributeDef::new(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyClassAttributeFactory({
|
||||
fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject {
|
||||
#deprecations
|
||||
pyo3::IntoPy::into_py(#cls::#name(), py)
|
||||
}
|
||||
__wrap
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_call_setter(cls: &syn::Type, spec: &FnSpec) -> syn::Result<TokenStream> {
|
||||
|
@ -306,452 +178,121 @@ fn impl_call_setter(cls: &syn::Type, spec: &FnSpec) -> syn::Result<TokenStream>
|
|||
Ok(fncall)
|
||||
}
|
||||
|
||||
/// Generate a function wrapper called `__wrap` for a property setter
|
||||
pub(crate) fn impl_wrap_setter(
|
||||
cls: &syn::Type,
|
||||
property_type: PropertyType,
|
||||
self_ty: &SelfType,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let setter_impl = match &property_type {
|
||||
PropertyType::Descriptor(ident) => {
|
||||
// Used here for PropertyType::Function, used in pyclass for descriptors.
|
||||
pub fn impl_py_setter_def(cls: &syn::Type, property_type: PropertyType) -> Result<TokenStream> {
|
||||
let python_name = property_type.null_terminated_python_name()?;
|
||||
let deprecations = property_type.deprecations();
|
||||
let doc = property_type.doc();
|
||||
let setter_impl = match property_type {
|
||||
PropertyType::Descriptor {
|
||||
field: syn::Field {
|
||||
ident: Some(ident), ..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
// named struct field
|
||||
quote!({ _slf.#ident = _val; })
|
||||
}
|
||||
PropertyType::Function(spec) => impl_call_setter(cls, spec)?,
|
||||
PropertyType::Descriptor { field_index, .. } => {
|
||||
// tuple struct field
|
||||
let index = syn::Index::from(field_index);
|
||||
quote!({ _slf.#index = _val; })
|
||||
}
|
||||
PropertyType::Function { spec, .. } => impl_call_setter(cls, spec)?,
|
||||
};
|
||||
|
||||
let slf = self_ty.receiver(cls);
|
||||
Ok(quote! {{
|
||||
#[allow(unused_mut)]
|
||||
unsafe extern "C" fn __wrap(
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_value: *mut pyo3::ffi::PyObject, _: *mut std::os::raw::c_void) -> std::os::raw::c_int
|
||||
{
|
||||
pyo3::callback::handle_panic(|_py| {
|
||||
#slf
|
||||
let _value = _py.from_borrowed_ptr::<pyo3::types::PyAny>(_value);
|
||||
let _val = pyo3::FromPyObject::extract(_value)?;
|
||||
|
||||
pyo3::callback::convert(_py, #setter_impl)
|
||||
})
|
||||
}
|
||||
__wrap
|
||||
}})
|
||||
}
|
||||
|
||||
/// This function abstracts away some copied code and can propably be simplified itself
|
||||
pub fn get_arg_names(spec: &FnSpec) -> Vec<syn::Ident> {
|
||||
(0..spec.args.len())
|
||||
.map(|pos| syn::Ident::new(&format!("arg{}", pos), Span::call_site()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn impl_call(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
|
||||
let fname = &spec.name;
|
||||
let names = get_arg_names(spec);
|
||||
quote! { pyo3::callback::convert(_py, #cls::#fname(_slf, #(#names),*)) }
|
||||
}
|
||||
|
||||
pub fn impl_arg_params(
|
||||
spec: &FnSpec<'_>,
|
||||
self_: Option<&syn::Type>,
|
||||
body: TokenStream,
|
||||
py: &syn::Ident,
|
||||
) -> Result<TokenStream> {
|
||||
if spec.args.is_empty() {
|
||||
return Ok(body);
|
||||
}
|
||||
|
||||
let mut positional_parameter_names = Vec::new();
|
||||
let mut required_positional_parameters = 0usize;
|
||||
let mut keyword_only_parameters = Vec::new();
|
||||
|
||||
for arg in spec.args.iter() {
|
||||
if arg.py || spec.is_args(&arg.name) || spec.is_kwargs(&arg.name) {
|
||||
continue;
|
||||
}
|
||||
let name = arg.name.unraw().to_string();
|
||||
let kwonly = spec.is_kw_only(&arg.name);
|
||||
let required = !(arg.optional.is_some() || spec.default_value(&arg.name).is_some());
|
||||
|
||||
if kwonly {
|
||||
keyword_only_parameters.push(quote! {
|
||||
pyo3::derive_utils::KeywordOnlyParameterDescription {
|
||||
name: #name,
|
||||
required: #required,
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if required {
|
||||
required_positional_parameters += 1;
|
||||
}
|
||||
positional_parameter_names.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
let num_params = positional_parameter_names.len() + keyword_only_parameters.len();
|
||||
let args_array = syn::Ident::new("output", Span::call_site());
|
||||
|
||||
let mut param_conversion = Vec::new();
|
||||
let mut option_pos = 0;
|
||||
for (idx, arg) in spec.args.iter().enumerate() {
|
||||
param_conversion.push(impl_arg_param(
|
||||
&arg,
|
||||
&spec,
|
||||
idx,
|
||||
self_,
|
||||
&mut option_pos,
|
||||
py,
|
||||
&args_array,
|
||||
)?);
|
||||
}
|
||||
|
||||
let (mut accept_args, mut accept_kwargs) = (false, false);
|
||||
|
||||
for s in spec.attrs.iter() {
|
||||
use crate::pyfunction::Argument;
|
||||
match s {
|
||||
Argument::VarArgs(_) => accept_args = true,
|
||||
Argument::KeywordArgs(_) => accept_kwargs = true,
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
let cls_name = if let Some(cls) = self_ {
|
||||
quote! { Some(<#cls as pyo3::type_object::PyTypeInfo>::NAME) }
|
||||
} else {
|
||||
quote! { None }
|
||||
let slf = match property_type {
|
||||
PropertyType::Descriptor { .. } => SelfType::Receiver { mutable: true }.receiver(cls),
|
||||
PropertyType::Function { self_type, .. } => self_type.receiver(cls),
|
||||
};
|
||||
let python_name = &spec.python_name;
|
||||
|
||||
// create array of arguments, and then parse
|
||||
Ok(quote! {
|
||||
{
|
||||
const DESCRIPTION: pyo3::derive_utils::FunctionDescription = pyo3::derive_utils::FunctionDescription {
|
||||
cls_name: #cls_name,
|
||||
func_name: stringify!(#python_name),
|
||||
positional_parameter_names: &[#(#positional_parameter_names),*],
|
||||
// TODO: https://github.com/PyO3/pyo3/issues/1439 - support specifying these
|
||||
positional_only_parameters: 0,
|
||||
required_positional_parameters: #required_positional_parameters,
|
||||
keyword_only_parameters: &[#(#keyword_only_parameters),*],
|
||||
accept_varargs: #accept_args,
|
||||
accept_varkeywords: #accept_kwargs,
|
||||
};
|
||||
|
||||
let mut #args_array = [None; #num_params];
|
||||
let (_args, _kwargs) = DESCRIPTION.extract_arguments(_args, _kwargs, &mut #args_array)?;
|
||||
|
||||
#(#param_conversion)*
|
||||
|
||||
#body
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// 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
|
||||
fn impl_arg_param(
|
||||
arg: &FnArg<'_>,
|
||||
spec: &FnSpec<'_>,
|
||||
idx: usize,
|
||||
self_: Option<&syn::Type>,
|
||||
option_pos: &mut usize,
|
||||
py: &syn::Ident,
|
||||
args_array: &syn::Ident,
|
||||
) -> Result<TokenStream> {
|
||||
// Use this macro inside this function, to ensure that all code generated here is associated
|
||||
// with the function argument
|
||||
macro_rules! quote_arg_span {
|
||||
($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => $($tokens)*) }
|
||||
}
|
||||
|
||||
let arg_name = syn::Ident::new(&format!("arg{}", idx), Span::call_site());
|
||||
|
||||
if arg.py {
|
||||
return Ok(quote_arg_span! { let #arg_name = #py; });
|
||||
}
|
||||
|
||||
let ty = arg.ty;
|
||||
let name = arg.name;
|
||||
let transform_error = quote_arg_span! {
|
||||
|e| pyo3::derive_utils::argument_extraction_error(#py, stringify!(#name), e)
|
||||
};
|
||||
|
||||
if spec.is_args(&name) {
|
||||
ensure_spanned!(
|
||||
arg.optional.is_none(),
|
||||
arg.name.span() => "args cannot be optional"
|
||||
);
|
||||
return Ok(quote_arg_span! {
|
||||
let #arg_name = _args.unwrap().extract().map_err(#transform_error)?;
|
||||
});
|
||||
} else if spec.is_kwargs(&name) {
|
||||
ensure_spanned!(
|
||||
arg.optional.is_some(),
|
||||
arg.name.span() => "kwargs must be Option<_>"
|
||||
);
|
||||
return Ok(quote_arg_span! {
|
||||
let #arg_name = _kwargs.map(|kwargs| kwargs.extract())
|
||||
.transpose()
|
||||
.map_err(#transform_error)?;
|
||||
});
|
||||
}
|
||||
|
||||
let arg_value = quote_arg_span!(#args_array[#option_pos]);
|
||||
*option_pos += 1;
|
||||
|
||||
let extract = if let Some(FromPyWithAttribute(expr_path)) = &arg.attrs.from_py_with {
|
||||
quote_arg_span! { #expr_path(_obj).map_err(#transform_error) }
|
||||
} else {
|
||||
quote_arg_span! { _obj.extract().map_err(#transform_error) }
|
||||
};
|
||||
|
||||
let arg_value_or_default = match (spec.default_value(name), arg.optional.is_some()) {
|
||||
(Some(default), true) if default.to_string() != "None" => {
|
||||
quote_arg_span! { #arg_value.map_or_else(|| Ok(Some(#default)), |_obj| #extract)? }
|
||||
}
|
||||
(Some(default), _) => {
|
||||
quote_arg_span! { #arg_value.map_or_else(|| Ok(#default), |_obj| #extract)? }
|
||||
}
|
||||
(None, true) => quote_arg_span! { #arg_value.map_or(Ok(None), |_obj| #extract)? },
|
||||
(None, false) => {
|
||||
quote_arg_span! {
|
||||
{
|
||||
let _obj = #arg_value.expect("Failed to extract required method argument");
|
||||
#extract?
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return if let syn::Type::Reference(tref) = arg.optional.as_ref().unwrap_or(&ty) {
|
||||
let (tref, mut_) = preprocess_tref(tref, self_);
|
||||
let (target_ty, borrow_tmp) = if arg.optional.is_some() {
|
||||
// Get Option<&T> from Option<PyRef<T>>
|
||||
(
|
||||
quote_arg_span! { Option<<#tref as pyo3::derive_utils::ExtractExt>::Target> },
|
||||
if mut_.is_some() {
|
||||
quote_arg_span! { _tmp.as_deref_mut() }
|
||||
} else {
|
||||
quote_arg_span! { _tmp.as_deref() }
|
||||
},
|
||||
)
|
||||
} else {
|
||||
// Get &T from PyRef<T>
|
||||
(
|
||||
quote_arg_span! { <#tref as pyo3::derive_utils::ExtractExt>::Target },
|
||||
quote_arg_span! { &#mut_ *_tmp },
|
||||
)
|
||||
};
|
||||
|
||||
Ok(quote_arg_span! {
|
||||
let #mut_ _tmp: #target_ty = #arg_value_or_default;
|
||||
let #arg_name = #borrow_tmp;
|
||||
})
|
||||
} else {
|
||||
Ok(quote_arg_span! {
|
||||
let #arg_name = #arg_value_or_default;
|
||||
})
|
||||
};
|
||||
|
||||
/// Replace `Self`, remove lifetime and get mutability from the type
|
||||
fn preprocess_tref(
|
||||
tref: &syn::TypeReference,
|
||||
self_: Option<&syn::Type>,
|
||||
) -> (syn::TypeReference, Option<syn::token::Mut>) {
|
||||
let mut tref = tref.to_owned();
|
||||
if let Some(syn::Type::Path(tpath)) = self_ {
|
||||
replace_self(&mut tref, &tpath.path);
|
||||
}
|
||||
tref.lifetime = None;
|
||||
let mut_ = tref.mutability;
|
||||
(tref, mut_)
|
||||
}
|
||||
|
||||
/// Replace `Self` with the exact type name since it is used out of the impl block
|
||||
fn replace_self(tref: &mut syn::TypeReference, self_path: &syn::Path) {
|
||||
match &mut *tref.elem {
|
||||
syn::Type::Reference(tref_inner) => replace_self(tref_inner, self_path),
|
||||
syn::Type::Path(tpath) => {
|
||||
if let Some(ident) = tpath.path.get_ident() {
|
||||
if ident == "Self" {
|
||||
tpath.path = self_path.to_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn impl_py_method_def(
|
||||
cls: &syn::Type,
|
||||
spec: &FnSpec,
|
||||
self_ty: &SelfType,
|
||||
flags: Option<TokenStream>,
|
||||
) -> Result<TokenStream> {
|
||||
let add_flags = flags.map(|flags| quote!(.flags(#flags)));
|
||||
let python_name = spec.null_terminated_python_name();
|
||||
let doc = &spec.doc;
|
||||
if spec.args.is_empty() {
|
||||
let wrapper = impl_wrap_noargs(cls, spec, self_ty);
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Method({
|
||||
pyo3::class::PyMethodDef::noargs(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyCFunction(#wrapper),
|
||||
#doc
|
||||
)
|
||||
#add_flags
|
||||
|
||||
})
|
||||
})
|
||||
} else {
|
||||
let wrapper = impl_wrap_cfunction_with_keywords(cls, &spec, self_ty)?;
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Method({
|
||||
pyo3::class::PyMethodDef::cfunction_with_keywords(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
||||
#doc
|
||||
)
|
||||
#add_flags
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
|
||||
let wrapper = impl_wrap_new(cls, &spec)?;
|
||||
Ok(quote! {
|
||||
impl pyo3::class::impl_::PyClassNewImpl<#cls> for pyo3::class::impl_::PyClassImplCollector<#cls> {
|
||||
fn new_impl(self) -> Option<pyo3::ffi::newfunc> {
|
||||
Some(#wrapper)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn impl_py_method_def_class(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
|
||||
let wrapper = impl_wrap_class(cls, &spec)?;
|
||||
let python_name = spec.null_terminated_python_name();
|
||||
let doc = &spec.doc;
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Class({
|
||||
pyo3::class::PyMethodDef::cfunction_with_keywords(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
||||
#doc
|
||||
).flags(pyo3::ffi::METH_CLASS)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn impl_py_method_def_static(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
|
||||
let wrapper = impl_wrap_static(cls, &spec)?;
|
||||
let python_name = spec.null_terminated_python_name();
|
||||
let doc = &spec.doc;
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Static({
|
||||
pyo3::class::PyMethodDef::cfunction_with_keywords(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
||||
#doc
|
||||
).flags(pyo3::ffi::METH_STATIC)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn impl_py_method_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenStream {
|
||||
let wrapper = impl_wrap_class_attribute(cls, &spec);
|
||||
let python_name = spec.null_terminated_python_name();
|
||||
quote! {
|
||||
pyo3::class::PyMethodDefType::ClassAttribute({
|
||||
pyo3::class::PyClassAttributeDef::new(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyClassAttributeFactory(#wrapper)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn impl_py_const_class_attribute(spec: &ConstSpec, wrapper: &TokenStream) -> TokenStream {
|
||||
let python_name = &spec.null_terminated_python_name();
|
||||
quote! {
|
||||
{
|
||||
pyo3::class::PyMethodDefType::ClassAttribute({
|
||||
pyo3::class::PyClassAttributeDef::new(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyClassAttributeFactory(#wrapper)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn impl_py_method_def_call(
|
||||
cls: &syn::Type,
|
||||
spec: &FnSpec,
|
||||
self_ty: &SelfType,
|
||||
) -> Result<TokenStream> {
|
||||
let wrapper = impl_wrap_cfunction_with_keywords(cls, &spec, self_ty)?;
|
||||
Ok(quote! {
|
||||
impl pyo3::class::impl_::PyClassCallImpl<#cls> for pyo3::class::impl_::PyClassImplCollector<#cls> {
|
||||
fn call_impl(self) -> Option<pyo3::ffi::PyCFunctionWithKeywords> {
|
||||
Some(#wrapper)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn impl_py_setter_def(
|
||||
cls: &syn::Type,
|
||||
property_type: PropertyType,
|
||||
self_ty: &SelfType,
|
||||
doc: &syn::LitStr,
|
||||
deprecations: &Deprecations,
|
||||
) -> Result<TokenStream> {
|
||||
let python_name = match property_type {
|
||||
PropertyType::Descriptor(ident) => {
|
||||
let formatted_name = format!("{}\0", ident.unraw());
|
||||
quote!(#formatted_name)
|
||||
}
|
||||
PropertyType::Function(spec) => spec.null_terminated_python_name(),
|
||||
};
|
||||
let wrapper = impl_wrap_setter(cls, property_type, self_ty)?;
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Setter({
|
||||
#deprecations
|
||||
pyo3::class::PySetterDef::new(
|
||||
#python_name,
|
||||
pyo3::class::methods::PySetter(#wrapper),
|
||||
pyo3::class::methods::PySetter({
|
||||
unsafe extern "C" fn __wrap(
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_value: *mut pyo3::ffi::PyObject,
|
||||
_: *mut std::os::raw::c_void
|
||||
) -> std::os::raw::c_int {
|
||||
pyo3::callback::handle_panic(|_py| {
|
||||
#slf
|
||||
let _value = _py.from_borrowed_ptr::<pyo3::types::PyAny>(_value);
|
||||
let _val = pyo3::FromPyObject::extract(_value)?;
|
||||
|
||||
pyo3::callback::convert(_py, #setter_impl)
|
||||
})
|
||||
}
|
||||
__wrap
|
||||
}),
|
||||
#doc
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn impl_py_getter_def(
|
||||
cls: &syn::Type,
|
||||
property_type: PropertyType,
|
||||
self_ty: &SelfType,
|
||||
doc: &syn::LitStr,
|
||||
deprecations: &Deprecations,
|
||||
) -> Result<TokenStream> {
|
||||
let python_name = match property_type {
|
||||
PropertyType::Descriptor(ident) => {
|
||||
let formatted_name = format!("{}\0", ident.unraw());
|
||||
quote!(#formatted_name)
|
||||
}
|
||||
PropertyType::Function(spec) => spec.null_terminated_python_name(),
|
||||
fn impl_call_getter(cls: &syn::Type, spec: &FnSpec) -> syn::Result<TokenStream> {
|
||||
let (py_arg, args) = split_off_python_arg(&spec.args);
|
||||
ensure_spanned!(
|
||||
args.is_empty(),
|
||||
args[0].ty.span() => "getter function can only have one argument (of type pyo3::Python)"
|
||||
);
|
||||
|
||||
let name = &spec.name;
|
||||
let fncall = if py_arg.is_some() {
|
||||
quote!(#cls::#name(_slf, _py))
|
||||
} else {
|
||||
quote!(#cls::#name(_slf))
|
||||
};
|
||||
|
||||
Ok(fncall)
|
||||
}
|
||||
|
||||
// Used here for PropertyType::Function, used in pyclass for descriptors.
|
||||
pub fn impl_py_getter_def(cls: &syn::Type, property_type: PropertyType) -> Result<TokenStream> {
|
||||
let python_name = property_type.null_terminated_python_name()?;
|
||||
let deprecations = property_type.deprecations();
|
||||
let doc = property_type.doc();
|
||||
let getter_impl = match property_type {
|
||||
PropertyType::Descriptor {
|
||||
field: syn::Field {
|
||||
ident: Some(ident), ..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
// named struct field
|
||||
quote!(_slf.#ident.clone())
|
||||
}
|
||||
PropertyType::Descriptor { field_index, .. } => {
|
||||
// tuple struct field
|
||||
let index = syn::Index::from(field_index);
|
||||
quote!(_slf.#index.clone())
|
||||
}
|
||||
PropertyType::Function { spec, .. } => impl_call_getter(cls, spec)?,
|
||||
};
|
||||
|
||||
let slf = match property_type {
|
||||
PropertyType::Descriptor { .. } => SelfType::Receiver { mutable: false }.receiver(cls),
|
||||
PropertyType::Function { self_type, .. } => self_type.receiver(cls),
|
||||
};
|
||||
let wrapper = impl_wrap_getter(cls, property_type, self_ty)?;
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Getter({
|
||||
#deprecations
|
||||
pyo3::class::PyGetterDef::new(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyGetter(#wrapper),
|
||||
pyo3::class::methods::PyGetter({
|
||||
unsafe extern "C" fn __wrap(
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_: *mut std::os::raw::c_void
|
||||
) -> *mut pyo3::ffi::PyObject {
|
||||
pyo3::callback::handle_panic(|_py| {
|
||||
#slf
|
||||
pyo3::callback::convert(_py, #getter_impl)
|
||||
})
|
||||
}
|
||||
__wrap
|
||||
}),
|
||||
#doc
|
||||
)
|
||||
})
|
||||
|
@ -762,7 +303,7 @@ pub(crate) fn impl_py_getter_def(
|
|||
fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&FnArg>, &[FnArg]) {
|
||||
if args
|
||||
.get(0)
|
||||
.map(|py| utils::is_python(&py.ty))
|
||||
.map(|py| utils::is_python(py.ty))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
(Some(&args[0]), &args[1..])
|
||||
|
@ -770,3 +311,53 @@ fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&FnArg>, &[FnArg])
|
|||
(None, args)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PropertyType<'a> {
|
||||
Descriptor {
|
||||
field_index: usize,
|
||||
field: &'a syn::Field,
|
||||
python_name: Option<&'a NameAttribute>,
|
||||
},
|
||||
Function {
|
||||
self_type: &'a SelfType,
|
||||
spec: &'a FnSpec<'a>,
|
||||
},
|
||||
}
|
||||
|
||||
impl PropertyType<'_> {
|
||||
fn null_terminated_python_name(&self) -> Result<syn::LitStr> {
|
||||
match self {
|
||||
PropertyType::Descriptor {
|
||||
field, python_name, ..
|
||||
} => {
|
||||
let name = match (python_name, &field.ident) {
|
||||
(Some(name), _) => name.0.to_string(),
|
||||
(None, Some(field_name)) => format!("{}\0", field_name.unraw()),
|
||||
(None, None) => {
|
||||
bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`")
|
||||
}
|
||||
};
|
||||
Ok(syn::LitStr::new(&name, field.span()))
|
||||
}
|
||||
PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name()),
|
||||
}
|
||||
}
|
||||
|
||||
fn deprecations(&self) -> Option<&Deprecations> {
|
||||
match self {
|
||||
PropertyType::Descriptor { .. } => None,
|
||||
PropertyType::Function { spec, .. } => Some(&spec.deprecations),
|
||||
}
|
||||
}
|
||||
|
||||
fn doc(&self) -> Cow<syn::LitStr> {
|
||||
match self {
|
||||
PropertyType::Descriptor { field, .. } => {
|
||||
let doc = utils::get_doc(&field.attrs, None)
|
||||
.unwrap_or_else(|_| syn::LitStr::new("", Span::call_site()));
|
||||
Cow::Owned(doc)
|
||||
}
|
||||
PropertyType::Function { spec, .. } => Cow::Borrowed(&spec.doc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,8 +73,8 @@ fn impl_proto_impl(
|
|||
None
|
||||
};
|
||||
|
||||
let method = if let FnType::Fn(self_ty) = &fn_spec.tp {
|
||||
pymethod::impl_py_method_def(ty, &fn_spec, &self_ty, flags)?
|
||||
let method = if let FnType::Fn(_) = &fn_spec.tp {
|
||||
pymethod::impl_py_method_def(ty, &fn_spec, flags)?
|
||||
} else {
|
||||
bail_spanned!(
|
||||
met.sig.span() => "expected method with receiver for #[pyproto] method"
|
||||
|
@ -133,7 +133,11 @@ fn impl_proto_methods(
|
|||
|
||||
let mut maybe_buffer_methods = None;
|
||||
|
||||
if cfg!(not(Py_3_9)) && proto.name == "Buffer" {
|
||||
let build_config = pyo3_build_config::get();
|
||||
const PY39: pyo3_build_config::PythonVersion =
|
||||
pyo3_build_config::PythonVersion { major: 3, minor: 9 };
|
||||
|
||||
if build_config.version <= PY39 && proto.name == "Buffer" {
|
||||
maybe_buffer_methods = Some(quote! {
|
||||
impl pyo3::class::impl_::PyBufferProtocolProcs<#ty>
|
||||
for pyo3::class::impl_::PyClassImplCollector<#ty>
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
use proc_macro2::Span;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
use crate::attributes::TextSignatureAttribute;
|
||||
|
||||
/// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span.
|
||||
macro_rules! err_spanned {
|
||||
($span:expr => $msg:expr) => {
|
||||
|
@ -27,12 +29,8 @@ macro_rules! ensure_spanned {
|
|||
}
|
||||
|
||||
/// Check if the given type `ty` is `pyo3::Python`.
|
||||
pub fn is_python(mut ty: &syn::Type) -> bool {
|
||||
while let syn::Type::Group(group) = ty {
|
||||
// Macros can create invisible delimiters around types.
|
||||
ty = &*group.elem;
|
||||
}
|
||||
match ty {
|
||||
pub fn is_python(ty: &syn::Type) -> bool {
|
||||
match unwrap_ty_group(ty) {
|
||||
syn::Type::Path(typath) => typath
|
||||
.path
|
||||
.segments
|
||||
|
@ -56,79 +54,19 @@ pub fn option_type_argument(ty: &syn::Type) -> Option<&syn::Type> {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn is_text_signature_attr(attr: &syn::Attribute) -> bool {
|
||||
attr.path.is_ident("text_signature")
|
||||
}
|
||||
|
||||
fn parse_text_signature_attr(
|
||||
attr: &syn::Attribute,
|
||||
python_name: &syn::Ident,
|
||||
) -> syn::Result<Option<syn::LitStr>> {
|
||||
if !is_text_signature_attr(attr) {
|
||||
return Ok(None);
|
||||
}
|
||||
let python_name_str = python_name.to_string();
|
||||
let python_name_str = python_name_str
|
||||
.rsplit('.')
|
||||
.next()
|
||||
.map(str::trim)
|
||||
.filter(|v| !v.is_empty())
|
||||
.ok_or_else(|| err_spanned!(python_name.span() => "failed to parse python name"))?;
|
||||
match attr.parse_meta()? {
|
||||
syn::Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
}) => {
|
||||
let value = lit.value();
|
||||
ensure_spanned!(
|
||||
value.starts_with('(') && value.ends_with(')'),
|
||||
lit.span() => "text_signature must start with \"(\" and end with \")\""
|
||||
);
|
||||
Ok(Some(syn::LitStr::new(
|
||||
&(python_name_str.to_owned() + &value),
|
||||
lit.span(),
|
||||
)))
|
||||
}
|
||||
meta => bail_spanned!(
|
||||
meta.span() => "text_signature must be of the form #[text_signature = \"\"]"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_text_signature_attrs(
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
python_name: &syn::Ident,
|
||||
) -> syn::Result<Option<syn::LitStr>> {
|
||||
let mut text_signature = None;
|
||||
let mut attrs_out = Vec::with_capacity(attrs.len());
|
||||
for attr in attrs.drain(..) {
|
||||
if let Some(value) = parse_text_signature_attr(&attr, python_name)? {
|
||||
ensure_spanned!(
|
||||
text_signature.is_none(),
|
||||
attr.span() => "text_signature attribute already specified previously"
|
||||
);
|
||||
text_signature = Some(value);
|
||||
} else {
|
||||
attrs_out.push(attr);
|
||||
}
|
||||
}
|
||||
*attrs = attrs_out;
|
||||
Ok(text_signature)
|
||||
}
|
||||
|
||||
// FIXME(althonos): not sure the docstring formatting is on par here.
|
||||
// Returns a null-terminated syn::LitStr for use as a Python docstring.
|
||||
pub fn get_doc(
|
||||
attrs: &[syn::Attribute],
|
||||
text_signature: Option<syn::LitStr>,
|
||||
null_terminated: bool,
|
||||
text_signature: Option<(&syn::Ident, &TextSignatureAttribute)>,
|
||||
) -> syn::Result<syn::LitStr> {
|
||||
let mut doc = String::new();
|
||||
let mut span = Span::call_site();
|
||||
|
||||
if let Some(text_signature) = text_signature {
|
||||
if let Some((python_name, text_signature)) = text_signature {
|
||||
// create special doc string lines to set `__text_signature__`
|
||||
span = text_signature.span();
|
||||
doc.push_str(&text_signature.value());
|
||||
doc.push_str(&python_name.to_string());
|
||||
span = text_signature.lit.span();
|
||||
doc.push_str(&text_signature.lit.value());
|
||||
doc.push_str("\n--\n\n");
|
||||
}
|
||||
|
||||
|
@ -136,35 +74,45 @@ pub fn get_doc(
|
|||
let mut first = true;
|
||||
|
||||
for attr in attrs.iter() {
|
||||
if let Ok(syn::Meta::NameValue(metanv)) = attr.parse_meta() {
|
||||
if metanv.path.is_ident("doc") {
|
||||
if let syn::Lit::Str(litstr) = metanv.lit {
|
||||
if first {
|
||||
first = false;
|
||||
span = litstr.span();
|
||||
}
|
||||
let d = litstr.value();
|
||||
doc.push_str(separator);
|
||||
if d.starts_with(' ') {
|
||||
doc.push_str(&d[1..d.len()]);
|
||||
} else {
|
||||
doc.push_str(&d);
|
||||
};
|
||||
separator = "\n";
|
||||
} else {
|
||||
bail_spanned!(metanv.span() => "invalid doc comment")
|
||||
if attr.path.is_ident("doc") {
|
||||
if let Ok(DocArgs { _eq_token, lit_str }) = syn::parse2(attr.tokens.clone()) {
|
||||
if first {
|
||||
first = false;
|
||||
span = lit_str.span();
|
||||
}
|
||||
let d = lit_str.value();
|
||||
doc.push_str(separator);
|
||||
if d.starts_with(' ') {
|
||||
doc.push_str(&d[1..d.len()]);
|
||||
} else {
|
||||
doc.push_str(&d);
|
||||
};
|
||||
separator = "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if null_terminated {
|
||||
doc.push('\0');
|
||||
}
|
||||
doc.push('\0');
|
||||
|
||||
Ok(syn::LitStr::new(&doc, span))
|
||||
}
|
||||
|
||||
struct DocArgs {
|
||||
_eq_token: syn::Token![=],
|
||||
lit_str: syn::LitStr,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for DocArgs {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let this = Self {
|
||||
_eq_token: input.parse()?,
|
||||
lit_str: input.parse()?,
|
||||
};
|
||||
ensure_spanned!(input.is_empty(), input.span() => "expected end of doc attribute");
|
||||
Ok(this)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensure_not_async_fn(sig: &syn::Signature) -> syn::Result<()> {
|
||||
if let Some(asyncness) = &sig.asyncness {
|
||||
bail_spanned!(
|
||||
|
@ -175,3 +123,17 @@ pub fn ensure_not_async_fn(sig: &syn::Signature) -> syn::Result<()> {
|
|||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unwrap_group(mut expr: &syn::Expr) -> &syn::Expr {
|
||||
while let syn::Expr::Group(g) = expr {
|
||||
expr = &*g.expr;
|
||||
}
|
||||
expr
|
||||
}
|
||||
|
||||
pub fn unwrap_ty_group(mut ty: &syn::Type) -> &syn::Type {
|
||||
while let syn::Type::Group(g) = ty {
|
||||
ty = &*g.elem;
|
||||
}
|
||||
ty
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3-macros"
|
||||
version = "0.13.2"
|
||||
version = "0.14.1"
|
||||
description = "Proc macros for PyO3 package"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||
|
@ -16,4 +16,4 @@ proc-macro = true
|
|||
[dependencies]
|
||||
quote = "1"
|
||||
syn = { version = "1", features = ["full", "extra-traits"] }
|
||||
pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.13.2" }
|
||||
pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.14.1" }
|
||||
|
|
|
@ -9,35 +9,55 @@ use proc_macro::TokenStream;
|
|||
use pyo3_macros_backend::{
|
||||
build_derive_from_pyobject, build_py_class, build_py_function, build_py_methods,
|
||||
build_py_proto, get_doc, process_functions_in_module, py_init, PyClassArgs, PyClassMethodsType,
|
||||
PyFunctionOptions,
|
||||
PyFunctionOptions, PyModuleOptions,
|
||||
};
|
||||
use quote::quote;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
/// A proc macro used to implement Python modules.
|
||||
///
|
||||
/// For more on creating Python modules
|
||||
/// see the [module section of the guide](https://pyo3.rs/main/module.html).
|
||||
/// The name of the module will be taken from the function name, unless `#[pyo3(name = "my_name")]`
|
||||
/// is also annotated on the function to override the name. **Important**: the module name should
|
||||
/// match the `lib.name` setting in `Cargo.toml`, so that Python is able to import the module
|
||||
/// without needing a custom import loader.
|
||||
///
|
||||
/// Functions annotated with `#[pymodule]` can also be annotated with the following:
|
||||
///
|
||||
/// | Annotation | Description |
|
||||
/// | :- | :- |
|
||||
/// | `#[pyo3(name = "...")]` | Defines the name of the module in Python. |
|
||||
///
|
||||
/// For more on creating Python modules see the [module section of the guide][1].
|
||||
///
|
||||
/// [1]: https://pyo3.rs/latest/module.html
|
||||
#[proc_macro_attribute]
|
||||
pub fn pymodule(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let mut ast = parse_macro_input!(input as syn::ItemFn);
|
||||
|
||||
let modname = if attr.is_empty() {
|
||||
ast.sig.ident.clone()
|
||||
let deprecated_pymodule_name_arg = if attr.is_empty() {
|
||||
None
|
||||
} else {
|
||||
parse_macro_input!(attr as syn::Ident)
|
||||
Some(parse_macro_input!(attr as syn::Ident))
|
||||
};
|
||||
|
||||
let options = match PyModuleOptions::from_pymodule_arg_and_attrs(
|
||||
deprecated_pymodule_name_arg,
|
||||
&mut ast.attrs,
|
||||
) {
|
||||
Ok(options) => options,
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
};
|
||||
|
||||
if let Err(err) = process_functions_in_module(&mut ast) {
|
||||
return err.to_compile_error().into();
|
||||
}
|
||||
|
||||
let doc = match get_doc(&ast.attrs, None, false) {
|
||||
let doc = match get_doc(&ast.attrs, None) {
|
||||
Ok(doc) => doc,
|
||||
Err(err) => return err.to_compile_error().into(),
|
||||
};
|
||||
|
||||
let expanded = py_init(&ast.sig.ident, &modname, doc);
|
||||
let expanded = py_init(&ast.sig.ident, options, doc);
|
||||
|
||||
quote!(
|
||||
#ast
|
||||
|
@ -86,11 +106,11 @@ pub fn pyproto(_: TokenStream, input: TokenStream) -> TokenStream {
|
|||
/// For more on creating Python classes,
|
||||
/// see the [class section of the guide][1].
|
||||
///
|
||||
/// [1]: https://pyo3.rs/main/class.html
|
||||
/// [2]: https://pyo3.rs/main/class.html#customizing-the-class
|
||||
/// [1]: https://pyo3.rs/latest/class.html
|
||||
/// [2]: https://pyo3.rs/latest/class.html#customizing-the-class
|
||||
/// [3]: std::marker::Send
|
||||
/// [4]: ../prelude/struct.PyAny.html
|
||||
/// [5]: https://pyo3.rs/main/class/protocols.html#garbage-collector-integration
|
||||
/// [5]: https://pyo3.rs/latest/class/protocols.html#garbage-collector-integration
|
||||
/// [6]: https://docs.python.org/3/library/weakref.html
|
||||
/// [7]: https://doc.rust-lang.org/nomicon/leaking.html
|
||||
/// [8]: std::rc::Rc
|
||||
|
@ -119,11 +139,11 @@ pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|||
/// For more on creating Python classes,
|
||||
/// see the [class section of the guide][1].
|
||||
///
|
||||
/// [1]: https://pyo3.rs/main/class.html
|
||||
/// [2]: https://pyo3.rs/main/class.html#customizing-the-class
|
||||
/// [1]: https://pyo3.rs/latest/class.html
|
||||
/// [2]: https://pyo3.rs/latest/class.html#customizing-the-class
|
||||
/// [3]: std::marker::Send
|
||||
/// [4]: ../prelude/struct.PyAny.html
|
||||
/// [5]: https://pyo3.rs/main/class/protocols.html#garbage-collector-integration
|
||||
/// [5]: https://pyo3.rs/latest/class/protocols.html#garbage-collector-integration
|
||||
/// [6]: https://docs.python.org/3/library/weakref.html
|
||||
/// [7]: https://doc.rust-lang.org/nomicon/leaking.html
|
||||
/// [8]: std::rc::Rc
|
||||
|
@ -155,17 +175,17 @@ pub fn pyclass_with_inventory(attr: TokenStream, input: TokenStream) -> TokenStr
|
|||
/// multiple `#[pymethods]` blocks for a single `#[pyclass]`.
|
||||
/// This will add a transitive dependency on the [`inventory`][3] crate.
|
||||
///
|
||||
/// [1]: https://pyo3.rs/main/class.html#instance-methods
|
||||
/// [2]: https://pyo3.rs/main/features.html#multiple-pymethods
|
||||
/// [1]: https://pyo3.rs/latest/class.html#instance-methods
|
||||
/// [2]: https://pyo3.rs/latest/features.html#multiple-pymethods
|
||||
/// [3]: https://docs.rs/inventory/
|
||||
/// [4]: https://pyo3.rs/main/class.html#constructor
|
||||
/// [5]: https://pyo3.rs/main/class.html#object-properties-using-getter-and-setter
|
||||
/// [6]: https://pyo3.rs/main/class.html#static-methods
|
||||
/// [7]: https://pyo3.rs/main/class.html#class-methods
|
||||
/// [8]: https://pyo3.rs/main/class.html#callable-objects
|
||||
/// [9]: https://pyo3.rs/main/class.html#class-attributes
|
||||
/// [10]: https://pyo3.rs/main/class.html#method-arguments
|
||||
/// [11]: https://pyo3.rs/main/class.html#object-properties-using-pyo3get-set
|
||||
/// [4]: https://pyo3.rs/latest/class.html#constructor
|
||||
/// [5]: https://pyo3.rs/latest/class.html#object-properties-using-getter-and-setter
|
||||
/// [6]: https://pyo3.rs/latest/class.html#static-methods
|
||||
/// [7]: https://pyo3.rs/latest/class.html#class-methods
|
||||
/// [8]: https://pyo3.rs/latest/class.html#callable-objects
|
||||
/// [9]: https://pyo3.rs/latest/class.html#class-attributes
|
||||
/// [10]: https://pyo3.rs/latest/class.html#method-arguments
|
||||
/// [11]: https://pyo3.rs/latest/class.html#object-properties-using-pyo3get-set
|
||||
#[proc_macro_attribute]
|
||||
pub fn pymethods(_: TokenStream, input: TokenStream) -> TokenStream {
|
||||
pymethods_impl(input, PyClassMethodsType::Specialization)
|
||||
|
@ -185,8 +205,8 @@ pub fn pymethods(_: TokenStream, input: TokenStream) -> TokenStream {
|
|||
/// | [`#[classattr]`][9] | Defines a class variable. |
|
||||
/// | [`#[args]`][10] | Define a method's default arguments and allows the function to receive `*args` and `**kwargs`. |
|
||||
///
|
||||
/// Methods within a `#[pymethods]` block can also be annotated with any of the attributes which can
|
||||
/// be used with [`#[pyfunction]`][attr.pyfunction.html].
|
||||
/// Methods within a `#[pymethods]` block can also be annotated with any of the `#[pyo3]` options which can
|
||||
/// be used with [`#[pyfunction]`][attr.pyfunction.html], except for `pass_module`.
|
||||
///
|
||||
/// For more on creating class methods see the [class section of the guide][1].
|
||||
///
|
||||
|
@ -194,17 +214,17 @@ pub fn pymethods(_: TokenStream, input: TokenStream) -> TokenStream {
|
|||
/// multiple `#[pymethods]` blocks for a single `#[pyclass]`.
|
||||
/// This will add a transitive dependency on the [`inventory`][3] crate.
|
||||
///
|
||||
/// [1]: https://pyo3.rs/main/class.html#instance-methods
|
||||
/// [2]: https://pyo3.rs/main/features.html#multiple-pymethods
|
||||
/// [1]: https://pyo3.rs/latest/class.html#instance-methods
|
||||
/// [2]: https://pyo3.rs/latest/features.html#multiple-pymethods
|
||||
/// [3]: https://docs.rs/inventory/
|
||||
/// [4]: https://pyo3.rs/main/class.html#constructor
|
||||
/// [5]: https://pyo3.rs/main/class.html#object-properties-using-getter-and-setter
|
||||
/// [6]: https://pyo3.rs/main/class.html#static-methods
|
||||
/// [7]: https://pyo3.rs/main/class.html#class-methods
|
||||
/// [8]: https://pyo3.rs/main/class.html#callable-objects
|
||||
/// [9]: https://pyo3.rs/main/class.html#class-attributes
|
||||
/// [10]: https://pyo3.rs/main/class.html#method-arguments
|
||||
/// [11]: https://pyo3.rs/main/class.html#object-properties-using-pyo3get-set
|
||||
/// [4]: https://pyo3.rs/latest/class.html#constructor
|
||||
/// [5]: https://pyo3.rs/latest/class.html#object-properties-using-getter-and-setter
|
||||
/// [6]: https://pyo3.rs/latest/class.html#static-methods
|
||||
/// [7]: https://pyo3.rs/latest/class.html#class-methods
|
||||
/// [8]: https://pyo3.rs/latest/class.html#callable-objects
|
||||
/// [9]: https://pyo3.rs/latest/class.html#class-attributes
|
||||
/// [10]: https://pyo3.rs/latest/class.html#method-arguments
|
||||
/// [11]: https://pyo3.rs/latest/class.html#object-properties-using-pyo3get-set
|
||||
#[proc_macro_attribute]
|
||||
pub fn pymethods_with_inventory(_: TokenStream, input: TokenStream) -> TokenStream {
|
||||
pymethods_impl(input, PyClassMethodsType::Inventory)
|
||||
|
@ -212,15 +232,17 @@ pub fn pymethods_with_inventory(_: TokenStream, input: TokenStream) -> TokenStre
|
|||
|
||||
/// A proc macro used to expose Rust functions to Python.
|
||||
///
|
||||
/// Functions annotated with `#[pyfunction]` can also be annotated with the following:
|
||||
/// Functions annotated with `#[pyfunction]` can also be annotated with the following `#[pyo3]` options:
|
||||
///
|
||||
/// | Annotation | Description |
|
||||
/// | :- | :- |
|
||||
/// | `#[pyo3(name = "...")]` | Defines the name of the function in Python. |
|
||||
/// | `#[pyo3(text_signature = "...")]` | Defines the `__text_signature__` attribute of the function in Python. |
|
||||
/// | `#[pyo3(pass_module)]` | Passes the module containing the function as a `&PyModule` first argument to the function. |
|
||||
///
|
||||
/// For more on exposing functions see the [function section of the guide][1].
|
||||
///
|
||||
/// [1]: https://pyo3.rs/main/function.html
|
||||
/// [1]: https://pyo3.rs/latest/function.html
|
||||
#[proc_macro_attribute]
|
||||
pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let mut ast = parse_macro_input!(input as syn::ItemFn);
|
||||
|
|
|
@ -55,16 +55,23 @@ impl<T> Debug for PyBuffer<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Represents the type of a Python buffer element.
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum ElementType {
|
||||
/// A signed integer type and its width in bytes.
|
||||
SignedInteger { bytes: usize },
|
||||
/// An unsigned integer type and its width in bytes.
|
||||
UnsignedInteger { bytes: usize },
|
||||
/// A boolean type.
|
||||
Bool,
|
||||
/// A float type and its width in bytes.
|
||||
Float { bytes: usize },
|
||||
/// An unknown type. This may occur when parsing has failed.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl ElementType {
|
||||
/// Determines the `ElementType` from a Python `struct` module format string.
|
||||
pub fn from_format(format: &CStr) -> ElementType {
|
||||
match format.to_bytes() {
|
||||
[char] | [b'@', char] => native_element_type_from_type_char(*char),
|
||||
|
@ -590,7 +597,7 @@ impl<T> Drop for PyBuffer<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Like `std::mem::cell`, but only provides read-only access to the data.
|
||||
/// Like [std::cell::Cell], but only provides read-only access to the data.
|
||||
///
|
||||
/// `&ReadOnlyCell<T>` is basically a safe version of `*const T`:
|
||||
/// The data cannot be modified through the reference, but other references may
|
||||
|
@ -599,11 +606,13 @@ impl<T> Drop for PyBuffer<T> {
|
|||
pub struct ReadOnlyCell<T: Element>(cell::UnsafeCell<T>);
|
||||
|
||||
impl<T: Element> ReadOnlyCell<T> {
|
||||
/// Returns a copy of the current value.
|
||||
#[inline]
|
||||
pub fn get(&self) -> T {
|
||||
unsafe { *self.0.get() }
|
||||
}
|
||||
|
||||
/// Returns a pointer to the current value.
|
||||
#[inline]
|
||||
pub fn as_ptr(&self) -> *const T {
|
||||
self.0.get()
|
||||
|
@ -638,7 +647,7 @@ impl_element!(f32, Float);
|
|||
impl_element!(f64, Float);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use super::PyBuffer;
|
||||
use crate::ffi;
|
||||
use crate::Python;
|
||||
|
@ -657,7 +666,7 @@ mod test {
|
|||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let bytes = py.eval("b'abcde'", None, None).unwrap();
|
||||
let buffer = PyBuffer::get(&bytes).unwrap();
|
||||
let buffer = PyBuffer::get(bytes).unwrap();
|
||||
assert_eq!(buffer.dimensions(), 1);
|
||||
assert_eq!(buffer.item_count(), 5);
|
||||
assert_eq!(buffer.format().to_str().unwrap(), "B");
|
||||
|
|
|
@ -12,14 +12,20 @@ use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput};
|
|||
use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyObject};
|
||||
use std::os::raw::c_int;
|
||||
|
||||
/// Operators for the __richcmp__ method
|
||||
/// Operators for the `__richcmp__` method
|
||||
#[derive(Debug)]
|
||||
pub enum CompareOp {
|
||||
/// The *less than* operator.
|
||||
Lt = ffi::Py_LT as isize,
|
||||
/// The *less than or equal to* operator.
|
||||
Le = ffi::Py_LE as isize,
|
||||
/// The equality operator.
|
||||
Eq = ffi::Py_EQ as isize,
|
||||
/// The *not equal to* operator.
|
||||
Ne = ffi::Py_NE as isize,
|
||||
/// The *greater than* operator.
|
||||
Gt = ffi::Py_GT as isize,
|
||||
/// The *greater than or equal to* operator.
|
||||
Ge = ffi::Py_GE as isize,
|
||||
}
|
||||
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
use crate::{
|
||||
ffi,
|
||||
impl_::freelist::FreeList,
|
||||
pycell::PyCellLayout,
|
||||
pyclass_init::PyObjectInit,
|
||||
type_object::{PyLayout, PyTypeObject},
|
||||
PyClass, PyMethodDefType, PyNativeType, PyTypeInfo,
|
||||
PyClass, PyMethodDefType, PyNativeType, PyTypeInfo, Python,
|
||||
};
|
||||
use std::{marker::PhantomData, thread};
|
||||
use std::{marker::PhantomData, os::raw::c_void, thread};
|
||||
|
||||
/// This type is used as a "dummy" type on which dtolnay specializations are
|
||||
/// applied to apply implementations from `#[pymethods]` & `#[pyproto]`
|
||||
|
@ -65,14 +66,20 @@ pub trait PyClassImpl: Sized {
|
|||
/// can be accessed by multiple threads by `threading` module.
|
||||
type ThreadChecker: PyClassThreadChecker<Self>;
|
||||
|
||||
fn for_each_method_def(_visitor: &mut dyn FnMut(&PyMethodDefType)) {}
|
||||
fn for_each_method_def(_visitor: &mut dyn FnMut(&[PyMethodDefType])) {}
|
||||
fn get_new() -> Option<ffi::newfunc> {
|
||||
None
|
||||
}
|
||||
fn get_call() -> Option<ffi::PyCFunctionWithKeywords> {
|
||||
None
|
||||
}
|
||||
fn for_each_proto_slot(_visitor: &mut dyn FnMut(&ffi::PyType_Slot)) {}
|
||||
fn get_alloc() -> Option<ffi::allocfunc> {
|
||||
None
|
||||
}
|
||||
fn get_free() -> Option<ffi::freefunc> {
|
||||
None
|
||||
}
|
||||
fn for_each_proto_slot(_visitor: &mut dyn FnMut(&[ffi::PyType_Slot])) {}
|
||||
fn get_buffer() -> Option<&'static PyBufferProcs> {
|
||||
None
|
||||
}
|
||||
|
@ -100,6 +107,112 @@ impl<T> PyClassCallImpl<T> for &'_ PyClassImplCollector<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait PyClassAllocImpl<T> {
|
||||
fn alloc_impl(self) -> Option<ffi::allocfunc>;
|
||||
}
|
||||
|
||||
impl<T> PyClassAllocImpl<T> for &'_ PyClassImplCollector<T> {
|
||||
fn alloc_impl(self) -> Option<ffi::allocfunc> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PyClassFreeImpl<T> {
|
||||
fn free_impl(self) -> Option<ffi::freefunc>;
|
||||
}
|
||||
|
||||
impl<T> PyClassFreeImpl<T> for &'_ PyClassImplCollector<T> {
|
||||
fn free_impl(self) -> Option<ffi::freefunc> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements a freelist.
|
||||
///
|
||||
/// Do not implement this trait manually. Instead, use `#[pyclass(freelist = N)]`
|
||||
/// on a Rust struct to implement it.
|
||||
pub trait PyClassWithFreeList: PyClass {
|
||||
fn get_free_list(py: Python) -> &mut FreeList<*mut ffi::PyObject>;
|
||||
}
|
||||
|
||||
/// Implementation of tp_alloc for `freelist` classes.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `subtype` must be a valid pointer to the type object of T or a subclass.
|
||||
/// - The GIL must be held.
|
||||
pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
|
||||
subtype: *mut ffi::PyTypeObject,
|
||||
nitems: ffi::Py_ssize_t,
|
||||
) -> *mut ffi::PyObject {
|
||||
let py = Python::assume_gil_acquired();
|
||||
|
||||
#[cfg(not(Py_3_8))]
|
||||
bpo_35810_workaround(py, subtype);
|
||||
|
||||
let self_type = T::type_object_raw(py);
|
||||
// If this type is a variable type or the subtype is not equal to this type, we cannot use the
|
||||
// freelist
|
||||
if nitems == 0 && subtype == self_type {
|
||||
if let Some(obj) = T::get_free_list(py).pop() {
|
||||
ffi::PyObject_Init(obj, subtype);
|
||||
return obj as _;
|
||||
}
|
||||
}
|
||||
|
||||
ffi::PyType_GenericAlloc(subtype, nitems)
|
||||
}
|
||||
|
||||
/// Implementation of tp_free for `freelist` classes.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `obj` must be a valid pointer to an instance of T (not a subclass).
|
||||
/// - The GIL must be held.
|
||||
#[allow(clippy::collapsible_if)] // for if cfg!
|
||||
pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
|
||||
let obj = obj as *mut ffi::PyObject;
|
||||
debug_assert_eq!(
|
||||
T::type_object_raw(Python::assume_gil_acquired()),
|
||||
ffi::Py_TYPE(obj)
|
||||
);
|
||||
if let Some(obj) = T::get_free_list(Python::assume_gil_acquired()).insert(obj) {
|
||||
let ty = ffi::Py_TYPE(obj);
|
||||
|
||||
// Deduce appropriate inverse of PyType_GenericAlloc
|
||||
let free = if ffi::PyType_IS_GC(ty) != 0 {
|
||||
ffi::PyObject_GC_Del
|
||||
} else {
|
||||
ffi::PyObject_Free
|
||||
};
|
||||
free(obj as *mut c_void);
|
||||
|
||||
if cfg!(Py_3_8) {
|
||||
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
|
||||
ffi::Py_DECREF(ty as *mut ffi::PyObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Workaround for Python issue 35810; no longer necessary in Python 3.8
|
||||
#[inline]
|
||||
#[cfg(not(Py_3_8))]
|
||||
unsafe fn bpo_35810_workaround(_py: Python, ty: *mut ffi::PyTypeObject) {
|
||||
#[cfg(Py_LIMITED_API)]
|
||||
{
|
||||
// Must check version at runtime for abi3 wheels - they could run against a higher version
|
||||
// than the build config suggests.
|
||||
use crate::once_cell::GILOnceCell;
|
||||
static IS_PYTHON_3_8: GILOnceCell<bool> = GILOnceCell::new();
|
||||
|
||||
if *IS_PYTHON_3_8.get_or_init(_py, || _py.version_info() >= (3, 8)) {
|
||||
// No fix needed - the wheel is running on a sufficiently new interpreter.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ffi::Py_INCREF(ty as *mut ffi::PyObject);
|
||||
}
|
||||
|
||||
// General methods implementation: either dtolnay specialization trait or inventory if
|
||||
// multiple-pymethods feature is enabled.
|
||||
|
||||
|
@ -216,6 +329,7 @@ pub struct ThreadCheckerStub<T: Send>(PhantomData<T>);
|
|||
|
||||
impl<T: Send> PyClassThreadChecker<T> for ThreadCheckerStub<T> {
|
||||
fn ensure(&self) {}
|
||||
#[inline]
|
||||
fn new() -> Self {
|
||||
ThreadCheckerStub(PhantomData)
|
||||
}
|
||||
|
@ -224,6 +338,7 @@ impl<T: Send> PyClassThreadChecker<T> for ThreadCheckerStub<T> {
|
|||
|
||||
impl<T: PyNativeType> PyClassThreadChecker<T> for ThreadCheckerStub<crate::PyObject> {
|
||||
fn ensure(&self) {}
|
||||
#[inline]
|
||||
fn new() -> Self {
|
||||
ThreadCheckerStub(PhantomData)
|
||||
}
|
||||
|
@ -279,8 +394,26 @@ pub trait PyClassBaseType: Sized {
|
|||
impl<T: PyClass> PyClassBaseType for T {
|
||||
type Dict = T::Dict;
|
||||
type WeakRef = T::WeakRef;
|
||||
type LayoutAsBase = crate::pycell::PyCellInner<T>;
|
||||
type LayoutAsBase = crate::pycell::PyCell<T>;
|
||||
type BaseNativeType = T::BaseNativeType;
|
||||
type ThreadChecker = T::ThreadChecker;
|
||||
type Initializer = crate::pyclass_init::PyClassInitializer<Self>;
|
||||
}
|
||||
|
||||
/// Default new implementation
|
||||
pub(crate) unsafe extern "C" fn fallback_new(
|
||||
_subtype: *mut ffi::PyTypeObject,
|
||||
_args: *mut ffi::PyObject,
|
||||
_kwds: *mut ffi::PyObject,
|
||||
) -> *mut ffi::PyObject {
|
||||
crate::callback_body!(py, {
|
||||
Err::<(), _>(crate::exceptions::PyTypeError::new_err(
|
||||
"No constructor defined",
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Implementation of tp_dealloc for all pyclasses
|
||||
pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
|
||||
crate::callback_body!(py, T::Layout::tp_dealloc(obj, py))
|
||||
}
|
||||
|
|
|
@ -78,7 +78,9 @@ py_unarys_func!(iternext, PyIterNextProtocol, Self::__next__);
|
|||
///
|
||||
/// See [`PyIterProtocol`](trait.PyIterProtocol.html) for an example.
|
||||
pub enum IterNextOutput<T, U> {
|
||||
/// The value yielded by the iterator.
|
||||
Yield(T),
|
||||
/// The `StopIteration` object.
|
||||
Return(U),
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ pub enum PyMethodDefType {
|
|||
pub enum PyMethodType {
|
||||
PyCFunction(PyCFunction),
|
||||
PyCFunctionWithKeywords(PyCFunctionWithKeywords),
|
||||
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
|
||||
PyCFunctionFastWithKeywords(PyCFunctionFastWithKeywords),
|
||||
}
|
||||
|
||||
// These newtype structs serve no purpose other than wrapping which are function pointers - because
|
||||
|
@ -36,6 +38,9 @@ pub enum PyMethodType {
|
|||
pub struct PyCFunction(pub ffi::PyCFunction);
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PyCFunctionWithKeywords(pub ffi::PyCFunctionWithKeywords);
|
||||
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PyCFunctionFastWithKeywords(pub ffi::_PyCFunctionFastWithKeywords);
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PyGetter(pub ffi::getter);
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
@ -105,6 +110,21 @@ impl PyMethodDef {
|
|||
}
|
||||
}
|
||||
|
||||
/// Define a function that can take `*args` and `**kwargs`.
|
||||
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
|
||||
pub const fn fastcall_cfunction_with_keywords(
|
||||
name: &'static str,
|
||||
cfunction: PyCFunctionFastWithKeywords,
|
||||
doc: &'static str,
|
||||
) -> Self {
|
||||
Self {
|
||||
ml_name: name,
|
||||
ml_meth: PyMethodType::PyCFunctionFastWithKeywords(cfunction),
|
||||
ml_flags: ffi::METH_FASTCALL | ffi::METH_KEYWORDS,
|
||||
ml_doc: doc,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn flags(mut self, flags: c_int) -> Self {
|
||||
self.ml_flags |= flags;
|
||||
self
|
||||
|
@ -115,6 +135,10 @@ impl PyMethodDef {
|
|||
let meth = match self.ml_meth {
|
||||
PyMethodType::PyCFunction(meth) => meth.0,
|
||||
PyMethodType::PyCFunctionWithKeywords(meth) => unsafe { std::mem::transmute(meth.0) },
|
||||
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
|
||||
PyMethodType::PyCFunctionFastWithKeywords(meth) => unsafe {
|
||||
std::mem::transmute(meth.0)
|
||||
},
|
||||
};
|
||||
|
||||
Ok(ffi::PyMethodDef {
|
||||
|
|
|
@ -98,11 +98,16 @@ py_unarys_func!(aiter, PyAsyncAiterProtocol, Self::__aiter__);
|
|||
py_unarys_func!(anext, PyAsyncAnextProtocol, Self::__anext__);
|
||||
|
||||
/// Output of `__anext__`.
|
||||
///
|
||||
/// <https://docs.python.org/3/reference/expressions.html#agen.__anext__>
|
||||
pub enum IterANextOutput<T, U> {
|
||||
/// An expression which the generator yielded.
|
||||
Yield(T),
|
||||
/// A `StopAsyncIteration` object.
|
||||
Return(U),
|
||||
}
|
||||
|
||||
/// An [IterANextOutput] of Python objects.
|
||||
pub type PyIterANextOutput = IterANextOutput<PyObject, PyObject>;
|
||||
|
||||
impl IntoPyCallbackOutput<*mut ffi::PyObject> for PyIterANextOutput {
|
||||
|
|
|
@ -543,7 +543,7 @@ where
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use crate::types::{IntoPyDict, PyAny, PyDict, PyList};
|
||||
use crate::{Python, ToPyObject};
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ mod min_const_generics {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::{
|
||||
panic,
|
||||
|
@ -257,7 +257,7 @@ fn invalid_sequence_length(expected: usize, actual: usize) -> PyErr {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use crate::{PyResult, Python};
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
//! Conversions to and from [indexmap](https://docs.rs/indexmap/)’s
|
||||
//! `IndexMap`.
|
||||
//!
|
||||
//! [`indexmap::IndexMap`] is a hash table that is closely compatible with the standard [`std::collections::HashMap`],
|
||||
//! with the difference that it preserves the insertion order when iterating over keys. It was inspired
|
||||
//! by Python's 3.6+ dict implementation.
|
||||
//!
|
||||
//! Dictionary order is guaranteed to be insertion order in Python, hence IndexMap is a good candidate
|
||||
//! for maintaining an equivalent behaviour in Rust.
|
||||
//!
|
||||
//! # Setup
|
||||
//!
|
||||
//! To use this feature, add this to your **`Cargo.toml`**:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! # change * to the latest versions
|
||||
//! indexmap = "*"
|
||||
// workaround for `extended_key_value_attributes`: https://github.com/rust-lang/rust/issues/82768#issuecomment-803935643
|
||||
#![cfg_attr(docsrs, cfg_attr(docsrs, doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"indexmap\"] }")))]
|
||||
#![cfg_attr(
|
||||
not(docsrs),
|
||||
doc = "pyo3 = { version = \"*\", features = [\"indexmap\"] }"
|
||||
)]
|
||||
//! ```
|
||||
//!
|
||||
//! Note that you must use compatible versions of indexmap and PyO3.
|
||||
//! The required indexmap version may vary based on the version of PyO3.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Using [indexmap](https://docs.rs/indexmap) to return a dictionary with some statistics
|
||||
//! about a list of numbers. Because of the insertion order guarantees, the Python code will
|
||||
//! always print the same result, matching users' expectations about Python's dict.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use indexmap::{indexmap, IndexMap};
|
||||
//! use pyo3::prelude::*;
|
||||
//!
|
||||
//! fn median(data: &Vec<i32>) -> f32 {
|
||||
//! let sorted_data = data.clone().sort();
|
||||
//! let mid = data.len() / 2;
|
||||
//! if (data.len() % 2 == 0) {
|
||||
//! data[mid] as f32
|
||||
//! }
|
||||
//! else {
|
||||
//! (data[mid] + data[mid - 1]) as f32 / 2.0
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn mean(data: &Vec<i32>) -> f32 {
|
||||
//! data.iter().sum::<i32>() as f32 / data.len() as f32
|
||||
//! }
|
||||
//! fn mode(data: &Vec<i32>) -> f32 {
|
||||
//! let mut frequency = IndexMap::new(); // we can use IndexMap as any hash table
|
||||
//!
|
||||
//! for &element in data {
|
||||
//! *frequency.entry(element).or_insert(0) += 1;
|
||||
//! }
|
||||
//!
|
||||
//! frequency
|
||||
//! .iter()
|
||||
//! .max_by(|a, b| a.1.cmp(&b.1))
|
||||
//! .map(|(k, _v)| *k)
|
||||
//! .unwrap() as f32
|
||||
//! }
|
||||
//!
|
||||
//! #[pyfunction]
|
||||
//! fn calculate_statistics(data: Vec<i32>) -> IndexMap<&'static str, f32> {
|
||||
//! indexmap!{
|
||||
//! "median" => median(&data),
|
||||
//! "mean" => mean(&data),
|
||||
//! "mode" => mode(&data),
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! #[pymodule]
|
||||
//! fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
//! m.add_function(wrap_pyfunction!(calculate_statistics, m)?)?;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Python code:
|
||||
//! ```python
|
||||
//! from my_module import calculate_statistics
|
||||
//!
|
||||
//! data = [1, 1, 1, 3, 4, 5]
|
||||
//! print(calculate_statistics(data))
|
||||
//! # always prints {"median": 2.0, "mean": 2.5, "mode": 1.0} in the same order
|
||||
//! # if another hash table was used, the order could be random
|
||||
//! ```
|
||||
|
||||
use crate::types::*;
|
||||
use crate::{FromPyObject, IntoPy, PyErr, PyObject, PyTryFrom, Python, ToPyObject};
|
||||
use std::{cmp, hash};
|
||||
|
||||
impl<K, V, H> ToPyObject for indexmap::IndexMap<K, V, H>
|
||||
where
|
||||
K: hash::Hash + cmp::Eq + ToPyObject,
|
||||
V: ToPyObject,
|
||||
H: hash::BuildHasher,
|
||||
{
|
||||
fn to_object(&self, py: Python) -> PyObject {
|
||||
IntoPyDict::into_py_dict(self, py).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, H> IntoPy<PyObject> for indexmap::IndexMap<K, V, H>
|
||||
where
|
||||
K: hash::Hash + cmp::Eq + IntoPy<PyObject>,
|
||||
V: IntoPy<PyObject>,
|
||||
H: hash::BuildHasher,
|
||||
{
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
let iter = self
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.into_py(py), v.into_py(py)));
|
||||
IntoPyDict::into_py_dict(iter, py).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'source, K, V, S> FromPyObject<'source> for indexmap::IndexMap<K, V, S>
|
||||
where
|
||||
K: FromPyObject<'source> + cmp::Eq + hash::Hash,
|
||||
V: FromPyObject<'source>,
|
||||
S: hash::BuildHasher + Default,
|
||||
{
|
||||
fn extract(ob: &'source PyAny) -> Result<Self, PyErr> {
|
||||
let dict = <PyDict as PyTryFrom>::try_from(ob)?;
|
||||
let mut ret = indexmap::IndexMap::with_capacity_and_hasher(dict.len(), S::default());
|
||||
for (k, v) in dict.iter() {
|
||||
ret.insert(K::extract(k)?, V::extract(v)?);
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_indexmap {
|
||||
|
||||
use crate::types::*;
|
||||
use crate::{IntoPy, PyObject, PyTryFrom, Python, ToPyObject};
|
||||
|
||||
#[test]
|
||||
fn test_indexmap_indexmap_to_python() {
|
||||
Python::with_gil(|py| {
|
||||
let mut map = indexmap::IndexMap::<i32, i32>::new();
|
||||
map.insert(1, 1);
|
||||
|
||||
let m = map.to_object(py);
|
||||
let py_map = <PyDict as PyTryFrom>::try_from(m.as_ref(py)).unwrap();
|
||||
|
||||
assert!(py_map.len() == 1);
|
||||
assert!(py_map.get_item(1).unwrap().extract::<i32>().unwrap() == 1);
|
||||
assert_eq!(
|
||||
map,
|
||||
py_map.extract::<indexmap::IndexMap::<i32, i32>>().unwrap()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_indexmap_indexmap_into_python() {
|
||||
Python::with_gil(|py| {
|
||||
let mut map = indexmap::IndexMap::<i32, i32>::new();
|
||||
map.insert(1, 1);
|
||||
|
||||
let m: PyObject = map.into_py(py);
|
||||
let py_map = <PyDict as PyTryFrom>::try_from(m.as_ref(py)).unwrap();
|
||||
|
||||
assert!(py_map.len() == 1);
|
||||
assert!(py_map.get_item(1).unwrap().extract::<i32>().unwrap() == 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_indexmap_indexmap_into_dict() {
|
||||
Python::with_gil(|py| {
|
||||
let mut map = indexmap::IndexMap::<i32, i32>::new();
|
||||
map.insert(1, 1);
|
||||
|
||||
let py_map = map.into_py_dict(py);
|
||||
|
||||
assert_eq!(py_map.len(), 1);
|
||||
assert_eq!(py_map.get_item(1).unwrap().extract::<i32>().unwrap(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_indexmap_indexmap_insertion_order_round_trip() {
|
||||
Python::with_gil(|py| {
|
||||
let n = 20;
|
||||
let mut map = indexmap::IndexMap::<i32, i32>::new();
|
||||
|
||||
for i in 1..=n {
|
||||
if i % 2 == 1 {
|
||||
map.insert(i, i);
|
||||
} else {
|
||||
map.insert(n - i, i);
|
||||
}
|
||||
}
|
||||
|
||||
let py_map = map.clone().into_py_dict(py);
|
||||
|
||||
let trip_map = py_map.extract::<indexmap::IndexMap<i32, i32>>().unwrap();
|
||||
|
||||
for (((k1, v1), (k2, v2)), (k3, v3)) in
|
||||
map.iter().zip(py_map.iter()).zip(trip_map.iter())
|
||||
{
|
||||
let k2 = k2.extract::<i32>().unwrap();
|
||||
let v2 = v2.extract::<i32>().unwrap();
|
||||
assert_eq!((k1, v1), (&k2, &v2));
|
||||
assert_eq!((k1, v1), (k3, v3));
|
||||
assert_eq!((&k2, &v2), (k3, v3));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
//! This module contains conversions between various Rust object and their representation in Python.
|
||||
|
||||
mod array;
|
||||
#[cfg(feature = "indexmap")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "indexmap")))]
|
||||
pub mod indexmap;
|
||||
mod osstr;
|
||||
mod path;
|
||||
|
|
|
@ -121,7 +121,7 @@ impl IntoPy<PyObject> for &'_ OsStr {
|
|||
impl ToPyObject for Cow<'_, OsStr> {
|
||||
#[inline]
|
||||
fn to_object(&self, py: Python) -> PyObject {
|
||||
(&self as &OsStr).to_object(py)
|
||||
(self as &OsStr).to_object(py)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,7 +135,7 @@ impl IntoPy<PyObject> for Cow<'_, OsStr> {
|
|||
impl ToPyObject for OsString {
|
||||
#[inline]
|
||||
fn to_object(&self, py: Python) -> PyObject {
|
||||
(&self as &OsStr).to_object(py)
|
||||
(self as &OsStr).to_object(py)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,8 +145,14 @@ impl IntoPy<PyObject> for OsString {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoPy<PyObject> for &'a OsString {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
self.to_object(py)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use crate::{types::PyString, IntoPy, PyObject, Python, ToPyObject};
|
||||
use std::fmt::Debug;
|
||||
use std::{
|
||||
|
@ -205,6 +211,7 @@ mod test {
|
|||
let os_str = OsStr::new("Hello\0\n🐍");
|
||||
test_roundtrip::<&OsStr>(py, os_str);
|
||||
test_roundtrip::<OsString>(py, os_str.to_os_string());
|
||||
test_roundtrip::<&OsString>(py, &os_str.to_os_string());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::{FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject};
|
||||
use crate::types::PyType;
|
||||
use crate::{FromPyObject, IntoPy, PyAny, PyNativeType, PyObject, PyResult, Python, ToPyObject};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -13,7 +14,21 @@ impl ToPyObject for Path {
|
|||
|
||||
impl FromPyObject<'_> for PathBuf {
|
||||
fn extract(ob: &PyAny) -> PyResult<Self> {
|
||||
Ok(PathBuf::from(OsString::extract(ob)?))
|
||||
let os_str = match OsString::extract(ob) {
|
||||
Ok(s) => s,
|
||||
Err(err) => {
|
||||
let py = ob.py();
|
||||
let pathlib = py.import("pathlib")?;
|
||||
let pathlib_path: &PyType = pathlib.getattr("Path")?.downcast()?;
|
||||
if pathlib_path.is_instance(ob)? {
|
||||
let path_str = ob.call_method0("__str__")?;
|
||||
OsString::extract(path_str)?
|
||||
} else {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(PathBuf::from(os_str))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,8 +66,14 @@ impl IntoPy<PyObject> for PathBuf {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoPy<PyObject> for &'a PathBuf {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
self.as_os_str().to_object(py)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use crate::{types::PyString, IntoPy, PyObject, Python, ToPyObject};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Debug;
|
||||
|
@ -105,11 +126,12 @@ mod test {
|
|||
let pystring: &PyString = pyobject.extract(py).unwrap();
|
||||
assert_eq!(pystring.to_string_lossy(), obj.as_ref().to_string_lossy());
|
||||
let roundtripped_obj: PathBuf = pystring.extract().unwrap();
|
||||
assert!(obj.as_ref() == roundtripped_obj.as_path());
|
||||
assert_eq!(obj.as_ref(), roundtripped_obj.as_path());
|
||||
}
|
||||
let path = Path::new("Hello\0\n🐍");
|
||||
test_roundtrip::<&Path>(py, path);
|
||||
test_roundtrip::<PathBuf>(py, path.to_path_buf());
|
||||
test_roundtrip::<&PathBuf>(py, &path.to_path_buf());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ impl FunctionDescription {
|
|||
format!("{}()", self.func_name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts the `args` and `kwargs` provided into `output`, according to this function
|
||||
/// definition.
|
||||
///
|
||||
|
@ -52,8 +53,9 @@ impl FunctionDescription {
|
|||
/// Unexpected, duplicate or invalid arguments will cause this function to return `TypeError`.
|
||||
pub fn extract_arguments<'p>(
|
||||
&self,
|
||||
args: &'p PyTuple,
|
||||
kwargs: Option<&'p PyDict>,
|
||||
py: Python<'p>,
|
||||
mut args: impl ExactSizeIterator<Item = &'p PyAny>,
|
||||
kwargs: Option<impl Iterator<Item = (&'p PyAny, &'p PyAny)>>,
|
||||
output: &mut [Option<&'p PyAny>],
|
||||
) -> PyResult<(Option<&'p PyTuple>, Option<&'p PyDict>)> {
|
||||
let num_positional_parameters = self.positional_parameter_names.len();
|
||||
|
@ -66,33 +68,36 @@ impl FunctionDescription {
|
|||
);
|
||||
|
||||
// Handle positional arguments
|
||||
let (args_provided, varargs) = {
|
||||
let args_provided = {
|
||||
let args_provided = args.len();
|
||||
|
||||
if self.accept_varargs {
|
||||
(
|
||||
std::cmp::min(num_positional_parameters, args_provided),
|
||||
Some(args.slice(num_positional_parameters as isize, args_provided as isize)),
|
||||
)
|
||||
std::cmp::min(num_positional_parameters, args_provided)
|
||||
} else if args_provided > num_positional_parameters {
|
||||
return Err(self.too_many_positional_arguments(args_provided));
|
||||
} else {
|
||||
(args_provided, None)
|
||||
args_provided
|
||||
}
|
||||
};
|
||||
|
||||
// Copy positional arguments into output
|
||||
for (out, arg) in output[..args_provided].iter_mut().zip(args) {
|
||||
for (out, arg) in output[..args_provided].iter_mut().zip(args.by_ref()) {
|
||||
*out = Some(arg);
|
||||
}
|
||||
|
||||
// Collect varargs into tuple
|
||||
let varargs = if self.accept_varargs {
|
||||
Some(PyTuple::new(py, args))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Handle keyword arguments
|
||||
let varkeywords = match (kwargs, self.accept_varkeywords) {
|
||||
(Some(kwargs), true) => {
|
||||
let mut varkeywords = None;
|
||||
self.extract_keyword_arguments(kwargs, output, |name, value| {
|
||||
varkeywords
|
||||
.get_or_insert_with(|| PyDict::new(kwargs.py()))
|
||||
.get_or_insert_with(|| PyDict::new(py))
|
||||
.set_item(name, value)
|
||||
})?;
|
||||
varkeywords
|
||||
|
@ -146,7 +151,7 @@ impl FunctionDescription {
|
|||
#[inline]
|
||||
fn extract_keyword_arguments<'p>(
|
||||
&self,
|
||||
kwargs: &'p PyDict,
|
||||
kwargs: impl Iterator<Item = (&'p PyAny, &'p PyAny)>,
|
||||
output: &mut [Option<&'p PyAny>],
|
||||
mut unexpected_keyword_handler: impl FnMut(&'p PyAny, &'p PyAny) -> PyResult<()>,
|
||||
) -> PyResult<()> {
|
||||
|
@ -289,7 +294,7 @@ impl ModuleDef {
|
|||
/// Make new module defenition with given module name.
|
||||
///
|
||||
/// # Safety
|
||||
/// `name` must be a null-terminated string.
|
||||
/// `name` and `doc` must be null-terminated strings.
|
||||
pub const unsafe fn new(name: &'static str, doc: &'static str) -> Self {
|
||||
const INIT: ffi::PyModuleDef = ffi::PyModuleDef {
|
||||
m_base: ffi::PyModuleDef_HEAD_INIT,
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
use crate::{
|
||||
exceptions::PyBaseException, ffi, types::PyType, IntoPy, IntoPyPointer, Py, PyObject, Python,
|
||||
exceptions::{PyBaseException, PyTypeError},
|
||||
ffi,
|
||||
type_object::PyTypeObject,
|
||||
types::PyType,
|
||||
AsPyPointer, IntoPy, IntoPyPointer, Py, PyObject, Python,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -10,7 +14,11 @@ pub(crate) struct PyErrStateNormalized {
|
|||
}
|
||||
|
||||
pub(crate) enum PyErrState {
|
||||
Lazy {
|
||||
LazyTypeAndValue {
|
||||
ptype: fn(Python) -> &PyType,
|
||||
pvalue: Box<dyn FnOnce(Python) -> PyObject + Send + Sync>,
|
||||
},
|
||||
LazyValue {
|
||||
ptype: Py<PyType>,
|
||||
pvalue: Box<dyn FnOnce(Python) -> PyObject + Send + Sync>,
|
||||
},
|
||||
|
@ -49,7 +57,19 @@ impl PyErrState {
|
|||
py: Python,
|
||||
) -> (*mut ffi::PyObject, *mut ffi::PyObject, *mut ffi::PyObject) {
|
||||
match self {
|
||||
PyErrState::Lazy { ptype, pvalue } => (
|
||||
PyErrState::LazyTypeAndValue { ptype, pvalue } => {
|
||||
let ty = ptype(py);
|
||||
if unsafe { ffi::PyExceptionClass_Check(ty.as_ptr()) } == 0 {
|
||||
Self::exceptions_must_derive_from_base_exception(py).into_ffi_tuple(py)
|
||||
} else {
|
||||
(
|
||||
ptype(py).into_ptr(),
|
||||
pvalue(py).into_ptr(),
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
}
|
||||
}
|
||||
PyErrState::LazyValue { ptype, pvalue } => (
|
||||
ptype.into_ptr(),
|
||||
pvalue(py).into_ptr(),
|
||||
std::ptr::null_mut(),
|
||||
|
@ -66,4 +86,12 @@ impl PyErrState {
|
|||
}) => (ptype.into_ptr(), pvalue.into_ptr(), ptraceback.into_ptr()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn exceptions_must_derive_from_base_exception(py: Python) -> Self {
|
||||
PyErrState::LazyValue {
|
||||
ptype: PyTypeError::type_object(py).into(),
|
||||
pvalue: boxed_args("exceptions must derive from BaseException"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
109
src/err/mod.rs
109
src/err/mod.rs
|
@ -81,7 +81,10 @@ impl PyErr {
|
|||
T: PyTypeObject,
|
||||
A: PyErrArguments + Send + Sync + 'static,
|
||||
{
|
||||
Python::with_gil(|py| PyErr::from_type(T::type_object(py), args))
|
||||
PyErr::from_state(PyErrState::LazyTypeAndValue {
|
||||
ptype: T::type_object,
|
||||
pvalue: boxed_args(args),
|
||||
})
|
||||
}
|
||||
|
||||
/// Constructs a new error, with the usual lazy initialization of Python exceptions.
|
||||
|
@ -97,7 +100,7 @@ impl PyErr {
|
|||
return exceptions_must_derive_from_base_exception(ty.py());
|
||||
}
|
||||
|
||||
PyErr::from_state(PyErrState::Lazy {
|
||||
PyErr::from_state(PyErrState::LazyValue {
|
||||
ptype: ty.into(),
|
||||
pvalue: boxed_args(args),
|
||||
})
|
||||
|
@ -319,7 +322,7 @@ impl PyErr {
|
|||
T: ToBorrowedObject,
|
||||
{
|
||||
exc.with_borrowed_ptr(py, |exc| unsafe {
|
||||
ffi::PyErr_GivenExceptionMatches(self.ptype_ptr(), exc) != 0
|
||||
ffi::PyErr_GivenExceptionMatches(self.ptype_ptr(py), exc) != 0
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -329,7 +332,7 @@ impl PyErr {
|
|||
T: PyTypeObject,
|
||||
{
|
||||
unsafe {
|
||||
ffi::PyErr_GivenExceptionMatches(self.ptype_ptr(), T::type_object(py).as_ptr()) != 0
|
||||
ffi::PyErr_GivenExceptionMatches(self.ptype_ptr(py), T::type_object(py).as_ptr()) != 0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -390,6 +393,28 @@ impl PyErr {
|
|||
PyErr::from_state(PyErrState::Normalized(self.normalized(py).clone()))
|
||||
}
|
||||
|
||||
/// Return the cause (either an exception instance, or None, set by `raise ... from ...`)
|
||||
/// associated with the exception, as accessible from Python through `__cause__`.
|
||||
pub fn cause(&self, py: Python) -> Option<PyErr> {
|
||||
let ptr = unsafe { ffi::PyException_GetCause(self.pvalue(py).as_ptr()) };
|
||||
let obj = unsafe { py.from_owned_ptr_or_opt::<PyAny>(ptr) };
|
||||
obj.map(|x| Self::from_instance(x))
|
||||
}
|
||||
|
||||
/// Set the cause associated with the exception, pass `None` to clear it.
|
||||
pub fn set_cause(&self, py: Python, cause: Option<Self>) {
|
||||
if let Some(cause) = cause {
|
||||
let cause = cause.into_instance(py);
|
||||
unsafe {
|
||||
ffi::PyException_SetCause(self.pvalue(py).as_ptr(), cause.as_ptr());
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
ffi::PyException_SetCause(self.pvalue(py).as_ptr(), std::ptr::null_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn from_state(state: PyErrState) -> PyErr {
|
||||
PyErr {
|
||||
state: UnsafeCell::new(Some(state)),
|
||||
|
@ -397,9 +422,12 @@ impl PyErr {
|
|||
}
|
||||
|
||||
/// Returns borrowed reference to this Err's type
|
||||
fn ptype_ptr(&self) -> *mut ffi::PyObject {
|
||||
fn ptype_ptr(&self, py: Python) -> *mut ffi::PyObject {
|
||||
match unsafe { &*self.state.get() } {
|
||||
Some(PyErrState::Lazy { ptype, .. }) => ptype.as_ptr(),
|
||||
// In lazy type case, normalize before returning ptype in case the type is not a valid
|
||||
// exception type.
|
||||
Some(PyErrState::LazyTypeAndValue { .. }) => self.normalized(py).ptype.as_ptr(),
|
||||
Some(PyErrState::LazyValue { ptype, .. }) => ptype.as_ptr(),
|
||||
Some(PyErrState::FfiTuple { ptype, .. }) => ptype.as_ptr(),
|
||||
Some(PyErrState::Normalized(n)) => n.ptype.as_ptr(),
|
||||
None => panic!("Cannot access exception type while normalizing"),
|
||||
|
@ -532,10 +560,7 @@ pub fn error_on_minusone(py: Python, result: c_int) -> PyResult<()> {
|
|||
|
||||
#[inline]
|
||||
fn exceptions_must_derive_from_base_exception(py: Python) -> PyErr {
|
||||
PyErr::from_state(PyErrState::Lazy {
|
||||
ptype: exceptions::PyTypeError::type_object(py).into(),
|
||||
pvalue: boxed_args("exceptions must derive from BaseException"),
|
||||
})
|
||||
PyErr::from_state(PyErrState::exceptions_must_derive_from_base_exception(py))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -545,13 +570,31 @@ mod tests {
|
|||
use crate::{PyErr, Python};
|
||||
|
||||
#[test]
|
||||
fn set_typeerror() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let err: PyErr = exceptions::PyTypeError::new_err(());
|
||||
err.restore(py);
|
||||
assert!(PyErr::occurred(py));
|
||||
drop(PyErr::fetch(py));
|
||||
fn set_valueerror() {
|
||||
Python::with_gil(|py| {
|
||||
let err: PyErr = exceptions::PyValueError::new_err("some exception message");
|
||||
assert!(err.is_instance::<exceptions::PyValueError>(py));
|
||||
err.restore(py);
|
||||
assert!(PyErr::occurred(py));
|
||||
let err = PyErr::fetch(py);
|
||||
assert!(err.is_instance::<exceptions::PyValueError>(py));
|
||||
assert_eq!(err.to_string(), "ValueError: some exception message");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_error_type() {
|
||||
Python::with_gil(|py| {
|
||||
let err: PyErr = PyErr::new::<crate::types::PyString, _>(());
|
||||
assert!(err.is_instance::<exceptions::PyTypeError>(py));
|
||||
err.restore(py);
|
||||
let err = PyErr::fetch(py);
|
||||
assert!(err.is_instance::<exceptions::PyTypeError>(py));
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"TypeError: exceptions must derive from BaseException"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -627,4 +670,36 @@ mod tests {
|
|||
is_send::<PyErrState>();
|
||||
is_sync::<PyErrState>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pyerr_cause() {
|
||||
Python::with_gil(|py| {
|
||||
let err = py
|
||||
.run("raise Exception('banana')", None, None)
|
||||
.expect_err("raising should have given us an error");
|
||||
assert!(err.cause(py).is_none());
|
||||
|
||||
let err = py
|
||||
.run(
|
||||
"raise Exception('banana') from Exception('apple')",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.expect_err("raising should have given us an error");
|
||||
let cause = err
|
||||
.cause(py)
|
||||
.expect("raising from should have given us a cause");
|
||||
assert_eq!(cause.to_string(), "Exception: apple");
|
||||
|
||||
err.set_cause(py, None);
|
||||
assert!(err.cause(py).is_none());
|
||||
|
||||
let new_cause = exceptions::PyValueError::new_err("orange");
|
||||
err.set_cause(py, Some(new_cause));
|
||||
let cause = err
|
||||
.cause(py)
|
||||
.expect("set_cause should have given us a cause");
|
||||
assert_eq!(cause.to_string(), "ValueError: orange");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ macro_rules! impl_exception_boilerplate {
|
|||
}
|
||||
|
||||
impl $name {
|
||||
/// Creates a new [PyErr](crate::PyErr) of this type.
|
||||
pub fn new_err<A>(args: A) -> $crate::PyErr
|
||||
where
|
||||
A: $crate::PyErrArguments + Send + Sync + 'static,
|
||||
|
@ -275,6 +276,7 @@ impl_native_exception!(PyIOError, PyExc_IOError);
|
|||
impl_native_exception!(PyWindowsError, PyExc_WindowsError);
|
||||
|
||||
impl PyUnicodeDecodeError {
|
||||
/// Creates a Python `UnicodeDecodeError`.
|
||||
pub fn new<'p>(
|
||||
py: Python<'p>,
|
||||
encoding: &CStr,
|
||||
|
@ -294,6 +296,7 @@ impl PyUnicodeDecodeError {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a Python `UnicodeDecodeError` from a Rust UTF-8 decoding error.
|
||||
pub fn new_utf8<'p>(
|
||||
py: Python<'p>,
|
||||
input: &[u8],
|
||||
|
@ -329,7 +332,7 @@ pub mod socket {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use super::{PyException, PyUnicodeDecodeError};
|
||||
use crate::types::{IntoPyDict, PyDict};
|
||||
use crate::{PyErr, Python};
|
||||
|
@ -399,7 +402,7 @@ mod test {
|
|||
let error_type = py.get_type::<CustomError>();
|
||||
let ctx = [("CustomError", error_type)].into_py_dict(py);
|
||||
let type_description: String = py
|
||||
.eval("str(CustomError)", None, Some(&ctx))
|
||||
.eval("str(CustomError)", None, Some(ctx))
|
||||
.unwrap()
|
||||
.extract()
|
||||
.unwrap();
|
||||
|
@ -407,7 +410,7 @@ mod test {
|
|||
py.run(
|
||||
"assert CustomError('oops').args == ('oops',)",
|
||||
None,
|
||||
Some(&ctx),
|
||||
Some(ctx),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
@ -2,10 +2,6 @@ use crate::ffi::object::PyObject;
|
|||
use crate::ffi::pystate::PyThreadState;
|
||||
use std::os::raw::{c_char, c_int, c_void};
|
||||
|
||||
// TODO: move to cpython
|
||||
pub type _PyFrameEvalFunction =
|
||||
extern "C" fn(*mut crate::ffi::PyFrameObject, c_int) -> *mut PyObject;
|
||||
|
||||
extern "C" {
|
||||
#[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))]
|
||||
#[cfg_attr(PyPy, link_name = "PyPyEval_CallObjectWithKeywords")]
|
||||
|
|
|
@ -8,6 +8,6 @@ extern "C" {
|
|||
exc: c_int,
|
||||
) -> *mut PyObject;
|
||||
pub fn _PyEval_RequestCodeExtraIndex(func: freefunc) -> c_int;
|
||||
pub fn PyEval_SetProfile(trace_func: Py_tracefunc, arg1: *mut PyObject);
|
||||
pub fn PyEval_SetTrace(trace_func: Py_tracefunc, arg1: *mut PyObject);
|
||||
pub fn PyEval_SetProfile(trace_func: Option<Py_tracefunc>, arg1: *mut PyObject);
|
||||
pub fn PyEval_SetTrace(trace_func: Option<Py_tracefunc>, arg1: *mut PyObject);
|
||||
}
|
||||
|
|
|
@ -15,17 +15,20 @@ pub struct PyListObject {
|
|||
|
||||
/// Macro, trading safety for speed
|
||||
#[inline]
|
||||
#[cfg(not(PyPy))]
|
||||
pub unsafe fn PyList_GET_ITEM(op: *mut PyObject, i: Py_ssize_t) -> *mut PyObject {
|
||||
*(*(op as *mut PyListObject)).ob_item.offset(i as isize)
|
||||
}
|
||||
|
||||
/// Macro, *only* to be used to fill in brand new lists
|
||||
#[inline]
|
||||
#[cfg(not(PyPy))]
|
||||
pub unsafe fn PyList_SET_ITEM(op: *mut PyObject, i: Py_ssize_t, v: *mut PyObject) {
|
||||
*(*(op as *mut PyListObject)).ob_item.offset(i as isize) = v;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(PyPy))]
|
||||
pub unsafe fn PyList_GET_SIZE(op: *mut PyObject) -> Py_ssize_t {
|
||||
Py_SIZE(op)
|
||||
}
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
pub mod abstract_;
|
||||
pub(crate) mod abstract_;
|
||||
// skipped bytearrayobject.h
|
||||
#[cfg(not(PyPy))]
|
||||
pub mod bytesobject;
|
||||
pub mod ceval;
|
||||
pub mod code;
|
||||
pub mod compile;
|
||||
pub(crate) mod bytesobject;
|
||||
pub(crate) mod ceval;
|
||||
pub(crate) mod code;
|
||||
pub(crate) mod compile;
|
||||
#[cfg(not(PyPy))]
|
||||
pub mod dictobject;
|
||||
pub(crate) mod dictobject;
|
||||
// skipped fileobject.h
|
||||
pub mod frameobject;
|
||||
pub mod import;
|
||||
pub(crate) mod frameobject;
|
||||
pub(crate) mod import;
|
||||
#[cfg(all(Py_3_8, not(PyPy)))]
|
||||
pub mod initconfig;
|
||||
pub(crate) mod initconfig;
|
||||
// skipped interpreteridobject.h
|
||||
pub mod listobject;
|
||||
pub mod object;
|
||||
pub mod pydebug;
|
||||
pub(crate) mod listobject;
|
||||
pub(crate) mod object;
|
||||
pub(crate) mod pydebug;
|
||||
#[cfg(all(Py_3_8, not(PyPy)))]
|
||||
pub mod pylifecycle;
|
||||
pub(crate) mod pylifecycle;
|
||||
|
||||
#[cfg(all(Py_3_8, not(PyPy)))]
|
||||
pub(crate) mod pystate;
|
||||
|
||||
pub use self::abstract_::*;
|
||||
#[cfg(not(PyPy))]
|
||||
|
@ -36,3 +39,6 @@ pub use self::object::*;
|
|||
pub use self::pydebug::*;
|
||||
#[cfg(all(Py_3_8, not(PyPy)))]
|
||||
pub use self::pylifecycle::*;
|
||||
|
||||
#[cfg(all(Py_3_8, not(PyPy)))]
|
||||
pub use self::pystate::*;
|
||||
|
|
|
@ -16,6 +16,9 @@ mod bufferinfo {
|
|||
use std::os::raw::{c_char, c_int, c_void};
|
||||
use std::ptr;
|
||||
|
||||
#[cfg(PyPy)]
|
||||
const Py_MAX_NDIMS: usize = 36;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Py_buffer {
|
||||
|
@ -31,6 +34,12 @@ mod bufferinfo {
|
|||
pub strides: *mut Py_ssize_t,
|
||||
pub suboffsets: *mut Py_ssize_t,
|
||||
pub internal: *mut c_void,
|
||||
#[cfg(PyPy)]
|
||||
pub flags: c_int,
|
||||
#[cfg(PyPy)]
|
||||
pub _strides: [Py_ssize_t; Py_MAX_NDIMS],
|
||||
#[cfg(PyPy)]
|
||||
pub _shape: [Py_ssize_t; Py_MAX_NDIMS],
|
||||
}
|
||||
|
||||
impl Py_buffer {
|
||||
|
@ -47,6 +56,12 @@ mod bufferinfo {
|
|||
strides: ptr::null_mut(),
|
||||
suboffsets: ptr::null_mut(),
|
||||
internal: ptr::null_mut(),
|
||||
#[cfg(PyPy)]
|
||||
flags: 0,
|
||||
#[cfg(PyPy)]
|
||||
_strides: [0; Py_MAX_NDIMS],
|
||||
#[cfg(PyPy)]
|
||||
_shape: [0; Py_MAX_NDIMS],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
use crate::ffi::pystate::{PyInterpreterState, PyThreadState};
|
||||
use std::os::raw::c_int;
|
||||
|
||||
// Py_tracefunc is defined in ffi::pystate
|
||||
|
||||
pub const PyTrace_CALL: c_int = 0;
|
||||
pub const PyTrace_EXCEPTION: c_int = 1;
|
||||
pub const PyTrace_LINE: c_int = 2;
|
||||
pub const PyTrace_RETURN: c_int = 3;
|
||||
pub const PyTrace_C_CALL: c_int = 4;
|
||||
pub const PyTrace_C_EXCEPTION: c_int = 5;
|
||||
pub const PyTrace_C_RETURN: c_int = 6;
|
||||
pub const PyTrace_OPCODE: c_int = 7;
|
||||
|
||||
extern "C" {
|
||||
// PyGILState_Check is defined in ffi::pystate
|
||||
pub fn PyInterpreterState_Main() -> *mut PyInterpreterState;
|
||||
pub fn PyInterpreterState_Head() -> *mut PyInterpreterState;
|
||||
pub fn PyInterpreterState_Next(interp: *mut PyInterpreterState) -> *mut PyInterpreterState;
|
||||
pub fn PyInterpreterState_ThreadHead(interp: *mut PyInterpreterState) -> *mut PyThreadState;
|
||||
pub fn PyThreadState_Next(tstate: *mut PyThreadState) -> *mut PyThreadState;
|
||||
}
|
||||
|
||||
#[cfg(Py_3_9)]
|
||||
#[cfg_attr(docsrs, doc(cfg(Py_3_9)))]
|
||||
pub type _PyFrameEvalFunction = extern "C" fn(
|
||||
*mut crate::ffi::PyThreadState,
|
||||
*mut crate::ffi::PyFrameObject,
|
||||
c_int,
|
||||
) -> *mut crate::ffi::object::PyObject;
|
||||
|
||||
#[cfg(Py_3_9)]
|
||||
extern "C" {
|
||||
/// Get the frame evaluation function.
|
||||
#[cfg_attr(docsrs, doc(cfg(Py_3_9)))]
|
||||
pub fn _PyInterpreterState_GetEvalFrameFunc(
|
||||
interp: *mut PyInterpreterState,
|
||||
) -> _PyFrameEvalFunction;
|
||||
|
||||
///Set the frame evaluation function.
|
||||
#[cfg_attr(docsrs, doc(cfg(Py_3_9)))]
|
||||
pub fn _PyInterpreterState_SetEvalFrameFunc(
|
||||
interp: *mut PyInterpreterState,
|
||||
eval_frame: _PyFrameEvalFunction,
|
||||
);
|
||||
}
|
|
@ -650,7 +650,6 @@ mod tests {
|
|||
fn test_date_fromtimestamp() {
|
||||
Python::with_gil(|py| {
|
||||
let args: Py<PyAny> = (100,).into_py(py);
|
||||
dbg!(args.as_ref(py));
|
||||
unsafe { PyDateTime_IMPORT() };
|
||||
let dt: &PyAny = unsafe { py.from_owned_ptr(PyDate_FromTimestamp(args.as_ptr())) };
|
||||
py_run!(
|
||||
|
|
|
@ -52,4 +52,15 @@ extern "C" {
|
|||
pub fn PyList_Reverse(arg1: *mut PyObject) -> c_int;
|
||||
#[cfg_attr(PyPy, link_name = "PyPyList_AsTuple")]
|
||||
pub fn PyList_AsTuple(arg1: *mut PyObject) -> *mut PyObject;
|
||||
|
||||
// CPython macros exported as functions on PyPy
|
||||
#[cfg(PyPy)]
|
||||
#[cfg_attr(PyPy, link_name = "PyPyList_GET_ITEM")]
|
||||
pub fn PyList_GET_ITEM(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject;
|
||||
#[cfg(PyPy)]
|
||||
#[cfg_attr(PyPy, link_name = "PyPyList_GET_SIZE")]
|
||||
pub fn PyList_GET_SIZE(arg1: *mut PyObject) -> Py_ssize_t;
|
||||
#[cfg(PyPy)]
|
||||
#[cfg_attr(PyPy, link_name = "PyPyList_SET_ITEM")]
|
||||
pub fn PyList_SET_ITEM(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject);
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ pub type PyCFunctionWithKeywords = unsafe extern "C" fn(
|
|||
kwds: *mut PyObject,
|
||||
) -> *mut PyObject;
|
||||
|
||||
#[cfg(Py_3_7)]
|
||||
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
|
||||
pub type _PyCFunctionFastWithKeywords = unsafe extern "C" fn(
|
||||
slf: *mut PyObject,
|
||||
args: *const *mut PyObject,
|
||||
|
|
|
@ -1,5 +1,27 @@
|
|||
//! Raw ffi declarations for the C interface of Python.
|
||||
//! Raw FFI declarations for Python's C API.
|
||||
//!
|
||||
//! This module provides low level bindings to the Python interpreter.
|
||||
//! It is meant for advanced users only - regular PyO3 users shouldn't
|
||||
//! need to interact with this module at all.
|
||||
//!
|
||||
//! The contents of this module are not documented here, as it would entail
|
||||
//! basically copying the documentation from CPython. Consult the [Python/C API Reference
|
||||
//! Manual][capi] for up-to-date documentation.
|
||||
//!
|
||||
//! # Safety
|
||||
//!
|
||||
//! The functions in this module lack individual safety documentation, but
|
||||
//! generally the following apply:
|
||||
//! - Pointer arguments have to point to a valid Python object of the correct type,
|
||||
//! although null pointers are sometimes valid input.
|
||||
//! - The vast majority can only be used safely while the GIL is held.
|
||||
//! - Some functions have additional safety requirements, consult the
|
||||
//! [Python/C API Reference Manual][capi]
|
||||
//! for more information.
|
||||
//!
|
||||
//! [capi]: https://docs.python.org/3/c-api/index.html
|
||||
#![allow(
|
||||
missing_docs,
|
||||
non_camel_case_types,
|
||||
non_snake_case,
|
||||
non_upper_case_globals,
|
||||
|
@ -27,6 +49,7 @@ pub use self::code::*;
|
|||
pub use self::codecs::*;
|
||||
pub use self::compile::*;
|
||||
pub use self::complexobject::*;
|
||||
#[cfg(all(Py_3_8, not(Py_LIMITED_API)))]
|
||||
pub use self::context::*;
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
pub use self::datetime::*;
|
||||
|
@ -78,6 +101,7 @@ pub use self::weakrefobject::*;
|
|||
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
pub use self::cpython::*;
|
||||
|
||||
mod abstract_;
|
||||
// skipped asdl.h
|
||||
// skipped ast.h
|
||||
|
@ -95,8 +119,6 @@ mod compile; // TODO: incomplete
|
|||
mod complexobject; // TODO supports PEP-384 only
|
||||
#[cfg(all(Py_3_8, not(Py_LIMITED_API)))]
|
||||
mod context; // It's actually 3.7.1, but no cfg for patches.
|
||||
#[cfg(not(all(Py_3_8, not(Py_LIMITED_API))))]
|
||||
mod context {}
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
pub(crate) mod datetime;
|
||||
mod descrobject; // TODO supports PEP-384 only
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::ffi::ceval::_PyFrameEvalFunction;
|
||||
use crate::ffi::moduleobject::PyModuleDef;
|
||||
use crate::ffi::object::PyObject;
|
||||
use crate::ffi::PyFrameObject;
|
||||
|
@ -6,12 +5,7 @@ use std::os::raw::{c_int, c_long};
|
|||
|
||||
pub const MAX_CO_EXTRA_USERS: c_int = 255;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct PyInterpreterState {
|
||||
pub ob_base: PyObject,
|
||||
pub eval_frame: _PyFrameEvalFunction,
|
||||
}
|
||||
opaque_struct!(PyInterpreterState);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
|
|
|
@ -47,7 +47,7 @@ extern "C" {
|
|||
|
||||
/// Macro, trading safety for speed
|
||||
#[inline]
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
pub unsafe fn PyTuple_GET_ITEM(op: *mut PyObject, i: Py_ssize_t) -> *mut PyObject {
|
||||
*(*(op as *mut PyTupleObject))
|
||||
.ob_item
|
||||
|
@ -56,14 +56,14 @@ pub unsafe fn PyTuple_GET_ITEM(op: *mut PyObject, i: Py_ssize_t) -> *mut PyObjec
|
|||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
pub unsafe fn PyTuple_GET_SIZE(op: *mut PyObject) -> Py_ssize_t {
|
||||
Py_SIZE(op)
|
||||
}
|
||||
|
||||
/// Macro, *only* to be used to fill in brand new tuples
|
||||
#[inline]
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
|
||||
pub unsafe fn PyTuple_SET_ITEM(op: *mut PyObject, i: Py_ssize_t, v: *mut PyObject) {
|
||||
*(*(op as *mut PyTupleObject))
|
||||
.ob_item
|
||||
|
|
115
src/freelist.rs
115
src/freelist.rs
|
@ -1,115 +0,0 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
//! Support for [free allocation lists][1].
|
||||
//!
|
||||
//! This can improve performance for types that are often created and deleted in quick succession.
|
||||
//!
|
||||
//! Rather than implementing this manually,
|
||||
//! implement it by annotating a struct with `#[pyclass(freelist = N)]`,
|
||||
//! where `N` is the size of the freelist.
|
||||
//!
|
||||
//! [1]: https://en.wikipedia.org/wiki/Free_list
|
||||
|
||||
use crate::class::impl_::PyClassImpl;
|
||||
use crate::pyclass::{get_type_free, tp_free_fallback, PyClassAlloc};
|
||||
use crate::type_object::{PyLayout, PyTypeInfo};
|
||||
use crate::{ffi, AsPyPointer, FromPyPointer, PyAny, Python};
|
||||
use std::mem;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
/// Implements a freelist.
|
||||
///
|
||||
/// Do not implement this trait manually. Instead, use `#[pyclass(freelist = N)]`
|
||||
/// on a Rust struct to implement it.
|
||||
pub trait PyClassWithFreeList {
|
||||
fn get_free_list(py: Python) -> &mut FreeList<*mut ffi::PyObject>;
|
||||
}
|
||||
|
||||
/// Represents a slot of a [`FreeList`].
|
||||
pub enum Slot<T> {
|
||||
Empty,
|
||||
Filled(T),
|
||||
}
|
||||
|
||||
pub struct FreeList<T> {
|
||||
entries: Vec<Slot<T>>,
|
||||
split: usize,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl<T> FreeList<T> {
|
||||
/// Creates a new `FreeList` instance with specified capacity.
|
||||
pub fn with_capacity(capacity: usize) -> FreeList<T> {
|
||||
let entries = (0..capacity).map(|_| Slot::Empty).collect::<Vec<_>>();
|
||||
|
||||
FreeList {
|
||||
entries,
|
||||
split: 0,
|
||||
capacity,
|
||||
}
|
||||
}
|
||||
|
||||
/// Pops the first non empty item.
|
||||
pub fn pop(&mut self) -> Option<T> {
|
||||
let idx = self.split;
|
||||
if idx == 0 {
|
||||
None
|
||||
} else {
|
||||
match mem::replace(&mut self.entries[idx - 1], Slot::Empty) {
|
||||
Slot::Filled(v) => {
|
||||
self.split = idx - 1;
|
||||
Some(v)
|
||||
}
|
||||
_ => panic!("FreeList is corrupt"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a value into the list. Returns `None` if the `FreeList` is full.
|
||||
pub fn insert(&mut self, val: T) -> Option<T> {
|
||||
let next = self.split + 1;
|
||||
if next < self.capacity {
|
||||
self.entries[self.split] = Slot::Filled(val);
|
||||
self.split = next;
|
||||
None
|
||||
} else {
|
||||
Some(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PyClassAlloc for T
|
||||
where
|
||||
T: PyTypeInfo + PyClassImpl + PyClassWithFreeList,
|
||||
{
|
||||
unsafe fn new(py: Python, subtype: *mut ffi::PyTypeObject) -> *mut Self::Layout {
|
||||
// if subtype is not equal to this type, we cannot use the freelist
|
||||
if subtype == Self::type_object_raw(py) {
|
||||
if let Some(obj) = <Self as PyClassWithFreeList>::get_free_list(py).pop() {
|
||||
ffi::PyObject_Init(obj, subtype);
|
||||
#[cfg(not(Py_3_8))]
|
||||
crate::pyclass::bpo_35810_workaround(py, subtype);
|
||||
return obj as _;
|
||||
}
|
||||
}
|
||||
crate::pyclass::default_new::<Self>(py, subtype) as _
|
||||
}
|
||||
|
||||
#[allow(clippy::collapsible_if)] // for if cfg!
|
||||
unsafe fn dealloc(py: Python, self_: *mut Self::Layout) {
|
||||
(*self_).py_drop(py);
|
||||
let obj = PyAny::from_borrowed_ptr_or_panic(py, self_ as _);
|
||||
|
||||
if let Some(obj) = <Self as PyClassWithFreeList>::get_free_list(py).insert(obj.as_ptr()) {
|
||||
let ty = ffi::Py_TYPE(obj);
|
||||
let free = get_type_free(ty).unwrap_or_else(|| tp_free_fallback(ty));
|
||||
free(obj as *mut c_void);
|
||||
|
||||
if cfg!(Py_3_8) {
|
||||
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
|
||||
ffi::Py_DECREF(ty as *mut ffi::PyObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -495,7 +495,7 @@ impl EnsureGIL {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use super::{gil_is_acquired, GILPool, GIL_COUNT, OWNED_OBJECTS, POOL};
|
||||
use crate::{ffi, gil, AsPyPointer, IntoPyPointer, PyObject, Python, ToPyObject};
|
||||
use std::ptr::NonNull;
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
//! any time without documentation in the CHANGELOG and without breaking semver guarantees.
|
||||
|
||||
pub mod deprecations;
|
||||
pub mod freelist;
|
||||
|
|
|
@ -11,3 +11,15 @@ pub const NAME_ATTRIBUTE: () = ();
|
|||
note = "use `#[pyfn(m)] #[pyo3(name = \"...\")]` instead of `#[pyfn(m, \"...\")]`"
|
||||
)]
|
||||
pub const PYFN_NAME_ARGUMENT: () = ();
|
||||
|
||||
#[deprecated(
|
||||
since = "0.14.0",
|
||||
note = "use `#[pymodule] #[pyo3(name = \"...\")]` instead of `#[pymodule(...)]`"
|
||||
)]
|
||||
pub const PYMODULE_NAME_ARGUMENT: () = ();
|
||||
|
||||
#[deprecated(
|
||||
since = "0.14.0",
|
||||
note = "use `#[pyo3(text_signature = \"...\")]` instead of `#[text_signature = \"...\"]`"
|
||||
)]
|
||||
pub const TEXT_SIGNATURE_ATTRIBUTE: () = ();
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
//! Support for [free allocation lists][1].
|
||||
//!
|
||||
//! This can improve performance for types that are often created and deleted in quick succession.
|
||||
//!
|
||||
//! Rather than implementing this manually,
|
||||
//! implement it by annotating a struct with `#[pyclass(freelist = N)]`,
|
||||
//! where `N` is the size of the freelist.
|
||||
//!
|
||||
//! [1]: https://en.wikipedia.org/wiki/Free_list
|
||||
|
||||
use std::mem;
|
||||
|
||||
/// Represents a slot of a [`FreeList`].
|
||||
pub enum Slot<T> {
|
||||
/// A free slot.
|
||||
Empty,
|
||||
/// An allocated slot.
|
||||
Filled(T),
|
||||
}
|
||||
|
||||
/// A free allocation list.
|
||||
///
|
||||
/// See [the parent module](crate::impl_::freelist) for more details.
|
||||
pub struct FreeList<T> {
|
||||
entries: Vec<Slot<T>>,
|
||||
split: usize,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl<T> FreeList<T> {
|
||||
/// Creates a new `FreeList` instance with specified capacity.
|
||||
pub fn with_capacity(capacity: usize) -> FreeList<T> {
|
||||
let entries = (0..capacity).map(|_| Slot::Empty).collect::<Vec<_>>();
|
||||
|
||||
FreeList {
|
||||
entries,
|
||||
split: 0,
|
||||
capacity,
|
||||
}
|
||||
}
|
||||
|
||||
/// Pops the first non empty item.
|
||||
pub fn pop(&mut self) -> Option<T> {
|
||||
let idx = self.split;
|
||||
if idx == 0 {
|
||||
None
|
||||
} else {
|
||||
match mem::replace(&mut self.entries[idx - 1], Slot::Empty) {
|
||||
Slot::Filled(v) => {
|
||||
self.split = idx - 1;
|
||||
Some(v)
|
||||
}
|
||||
_ => panic!("FreeList is corrupt"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a value into the list. Returns `None` if the `FreeList` is full.
|
||||
pub fn insert(&mut self, val: T) -> Option<T> {
|
||||
let next = self.split + 1;
|
||||
if next < self.capacity {
|
||||
self.entries[self.split] = Slot::Filled(val);
|
||||
self.split = next;
|
||||
None
|
||||
} else {
|
||||
Some(val)
|
||||
}
|
||||
}
|
||||
}
|
352
src/instance.rs
352
src/instance.rs
|
@ -18,6 +18,7 @@ use std::ptr::NonNull;
|
|||
/// to the GIL, which is why you can get a token from all references of those
|
||||
/// types.
|
||||
pub unsafe trait PyNativeType: Sized {
|
||||
/// Returns a GIL marker constrained to the lifetime of this type.
|
||||
fn py(&self) -> Python {
|
||||
unsafe { Python::assume_gil_acquired() }
|
||||
}
|
||||
|
@ -32,21 +33,145 @@ pub unsafe trait PyNativeType: Sized {
|
|||
}
|
||||
}
|
||||
|
||||
/// A Python object of known type T.
|
||||
/// A GIL-independent reference to an object allocated on the Python heap.
|
||||
///
|
||||
/// Accessing this object is thread-safe, since any access to its API requires a `Python<'py>` GIL
|
||||
/// token. There are a few different ways to use the Python object contained:
|
||||
/// - [`Py::as_ref`](#method.as_ref) to borrow a GIL-bound reference to the contained object.
|
||||
/// - [`Py::borrow`](#method.borrow), [`Py::try_borrow`](#method.try_borrow),
|
||||
/// [`Py::borrow_mut`](#method.borrow_mut), or [`Py::try_borrow_mut`](#method.try_borrow_mut),
|
||||
/// to directly access a `#[pyclass]` value (which has RefCell-like behavior, see
|
||||
/// [the `PyCell` guide entry](https://pyo3.rs/main/class.html#pycell-and-interior-mutability)
|
||||
/// ).
|
||||
/// - Use methods directly on `Py`, such as [`Py::call`](#method.call) and
|
||||
/// [`Py::call_method`](#method.call_method).
|
||||
/// This type does not auto-dereference to the inner object because you must prove you hold the GIL to access it.
|
||||
/// Instead, call one of its methods to access the inner object:
|
||||
/// - [`Py::as_ref`], to borrow a GIL-bound reference to the contained object.
|
||||
/// - [`Py::borrow`], [`Py::try_borrow`], [`Py::borrow_mut`], or [`Py::try_borrow_mut`],
|
||||
/// to get a (mutable) reference to a contained pyclass, using a scheme similar to std's [`RefCell`].
|
||||
/// See the [`PyCell` guide entry](https://pyo3.rs/latest/class.html#pycell-and-interior-mutability)
|
||||
/// for more information.
|
||||
/// - You can call methods directly on `Py` with [`Py::call`], [`Py::call_method`] and friends.
|
||||
/// These require passing in the [`Python<'py>`](crate::Python) token but are otherwise similar to the corresponding
|
||||
/// methods on [`PyAny`].
|
||||
///
|
||||
/// See [the guide](https://pyo3.rs/main/types.html) for an explanation
|
||||
/// of the different Python object types.
|
||||
/// # Example: Storing Python objects in structs
|
||||
///
|
||||
/// As all the native Python objects only appear as references, storing them in structs doesn't work well.
|
||||
/// For example, this won't compile:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use pyo3::prelude::*;
|
||||
/// # use pyo3::types::PyDict;
|
||||
/// #
|
||||
/// #[pyclass]
|
||||
/// struct Foo<'py> {
|
||||
/// inner: &'py PyDict,
|
||||
/// }
|
||||
///
|
||||
/// impl Foo {
|
||||
/// fn new() -> Foo {
|
||||
/// let foo = Python::with_gil(|py| {
|
||||
/// // `py` will only last for this scope.
|
||||
///
|
||||
/// // `&PyDict` derives its lifetime from `py` and
|
||||
/// // so won't be able to outlive this closure.
|
||||
/// let dict: &PyDict = PyDict::new(py);
|
||||
///
|
||||
/// // because `Foo` contains `dict` its lifetime
|
||||
/// // is now also tied to `py`.
|
||||
/// Foo { inner: dict }
|
||||
/// });
|
||||
/// // Foo is no longer valid.
|
||||
/// // Returning it from this function is a 💥 compiler error 💥
|
||||
/// foo
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`Py`]`<T>` can be used to get around this by converting `dict` into a GIL-independent reference:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use pyo3::prelude::*;
|
||||
/// # use pyo3::types::PyDict;
|
||||
/// #
|
||||
/// #[pyclass]
|
||||
/// struct Foo {
|
||||
/// inner: Py<PyDict>,
|
||||
/// }
|
||||
///
|
||||
/// impl Foo {
|
||||
/// fn new() -> Foo {
|
||||
/// Python::with_gil(|py| {
|
||||
/// let dict: Py<PyDict> = PyDict::new(py).into();
|
||||
/// Foo { inner: dict }
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This can also be done with other pyclasses:
|
||||
/// ```rust
|
||||
/// # use pyo3::prelude::*;
|
||||
/// #
|
||||
/// #[pyclass]
|
||||
/// struct Bar {/* fields omitted */}
|
||||
///
|
||||
/// #[pyclass]
|
||||
/// struct Foo {
|
||||
/// inner: Py<Bar>,
|
||||
/// }
|
||||
///
|
||||
/// impl Foo {
|
||||
/// fn new() -> PyResult<Foo> {
|
||||
/// Python::with_gil(|py| {
|
||||
/// let bar: Py<Bar> = Py::new(py, Bar {})?;
|
||||
/// Ok(Foo { inner: bar })
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Example: Shared ownership of Python objects
|
||||
///
|
||||
/// `Py<T>` can be used to share ownership of a Python object, similar to std's [`Rc`]`<T>`.
|
||||
/// As with [`Rc`]`<T>`, cloning it increases its reference count rather than duplicating
|
||||
/// the underlying object.
|
||||
///
|
||||
/// This can be done using either [`Py::clone_ref`] or [`Py`]`<T>`'s [`Clone`] trait implementation.
|
||||
/// [`Py::clone_ref`] will be faster if you happen to be already holding the GIL.
|
||||
///
|
||||
/// ```rust
|
||||
/// use pyo3::prelude::*;
|
||||
/// use pyo3::types::PyDict;
|
||||
/// use pyo3::conversion::AsPyPointer;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// Python::with_gil(|py| {
|
||||
/// let first: Py<PyDict> = PyDict::new(py).into();
|
||||
///
|
||||
/// // All of these are valid syntax
|
||||
/// let second = Py::clone_ref(&first, py);
|
||||
/// let third = first.clone_ref(py);
|
||||
/// let fourth = Py::clone(&first);
|
||||
/// let fifth = first.clone();
|
||||
///
|
||||
/// // Disposing of our original `Py<PyDict>` just decrements the reference count.
|
||||
/// drop(first);
|
||||
///
|
||||
/// // They all point to the same object
|
||||
/// assert_eq!(second.as_ptr(), third.as_ptr());
|
||||
/// assert_eq!(fourth.as_ptr(), fifth.as_ptr());
|
||||
/// assert_eq!(second.as_ptr(), fourth.as_ptr());
|
||||
/// });
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Preventing reference cycles
|
||||
///
|
||||
/// It is easy to accidentally create reference cycles using [`Py`]`<T>`.
|
||||
/// The Python interpreter can break these reference cycles within pyclasses if they
|
||||
/// implement the [`PyGCProtocol`](crate::class::gc::PyGCProtocol). If your pyclass
|
||||
/// contains other Python objects you should implement this protocol to avoid leaking memory.
|
||||
///
|
||||
/// # A note on `Send` and `Sync`
|
||||
///
|
||||
/// Accessing this object is threadsafe, since any access to its API requires a [`Python<'py>`](crate::Python) token.
|
||||
/// As you can only get this by acquiring the GIL, `Py<...>` "implements [`Send`] and [`Sync`].
|
||||
///
|
||||
/// [`Rc`]: std::rc::Rc
|
||||
/// [`RefCell`]: std::cell::RefCell
|
||||
#[repr(transparent)]
|
||||
pub struct Py<T>(NonNull<ffi::PyObject>, PhantomData<T>);
|
||||
|
||||
|
@ -57,7 +182,24 @@ impl<T> Py<T>
|
|||
where
|
||||
T: PyClass,
|
||||
{
|
||||
/// Create a new instance `Py<T>` of a `#[pyclass]` on the Python heap.
|
||||
/// Creates a new instance `Py<T>` of a `#[pyclass]` on the Python heap.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use pyo3::prelude::*;
|
||||
///
|
||||
/// #[pyclass]
|
||||
/// struct Foo {/* fields omitted */}
|
||||
///
|
||||
/// # fn main() -> PyResult<()> {
|
||||
/// Python::with_gil(|py| -> PyResult<Py<Foo>> {
|
||||
/// let foo: Py<Foo> = Py::new(py, Foo {})?;
|
||||
/// Ok(foo)
|
||||
/// })?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn new(py: Python, value: impl Into<PyClassInitializer<T>>) -> PyResult<Py<T>> {
|
||||
let initializer = value.into();
|
||||
let obj = initializer.create_cell(py)?;
|
||||
|
@ -70,56 +212,91 @@ impl<T> Py<T>
|
|||
where
|
||||
T: PyTypeInfo,
|
||||
{
|
||||
/// Borrows a GIL-bound reference to the contained `T`. By binding to the GIL lifetime, this
|
||||
/// allows the GIL-bound reference to not require `Python` for any of its methods.
|
||||
/// Borrows a GIL-bound reference to the contained `T`.
|
||||
///
|
||||
/// By binding to the GIL lifetime, this allows the GIL-bound reference to not require
|
||||
/// [`Python<'py>`](crate::Python) for any of its methods, which makes calling methods
|
||||
/// on it more ergonomic.
|
||||
///
|
||||
/// For native types, this reference is `&T`. For pyclasses, this is `&PyCell<T>`.
|
||||
///
|
||||
/// Note that the lifetime of the returned reference is the shortest of `&self` and
|
||||
/// [`Python<'py>`](crate::Python).
|
||||
/// Consider using [`Py::into_ref`] instead if this poses a problem.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Get access to `&PyList` from `Py<PyList>`:
|
||||
///
|
||||
/// ```
|
||||
/// # use pyo3::prelude::*;
|
||||
/// # use pyo3::types::PyList;
|
||||
/// # Python::with_gil(|py| {
|
||||
/// let list: Py<PyList> = PyList::empty(py).into();
|
||||
/// let list: &PyList = list.as_ref(py);
|
||||
/// assert_eq!(list.len(), 0);
|
||||
/// # });
|
||||
/// #
|
||||
/// Python::with_gil(|py| {
|
||||
/// let list: Py<PyList> = PyList::empty(py).into();
|
||||
/// let list: &PyList = list.as_ref(py);
|
||||
/// assert_eq!(list.len(), 0);
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// Get access to `&PyCell<MyClass>` from `Py<MyClass>`:
|
||||
///
|
||||
/// ```
|
||||
/// # use pyo3::prelude::*;
|
||||
/// #
|
||||
/// #[pyclass]
|
||||
/// struct MyClass { }
|
||||
/// # Python::with_gil(|py| {
|
||||
/// let my_class: Py<MyClass> = Py::new(py, MyClass { }).unwrap();
|
||||
/// let my_class_cell: &PyCell<MyClass> = my_class.as_ref(py);
|
||||
/// assert!(my_class_cell.try_borrow().is_ok());
|
||||
/// # });
|
||||
///
|
||||
/// Python::with_gil(|py| {
|
||||
/// let my_class: Py<MyClass> = Py::new(py, MyClass { }).unwrap();
|
||||
/// let my_class_cell: &PyCell<MyClass> = my_class.as_ref(py);
|
||||
/// assert!(my_class_cell.try_borrow().is_ok());
|
||||
/// });
|
||||
/// ```
|
||||
pub fn as_ref<'py>(&'py self, _py: Python<'py>) -> &'py T::AsRefTarget {
|
||||
let any = self.as_ptr() as *const PyAny;
|
||||
unsafe { PyNativeType::unchecked_downcast(&*any) }
|
||||
}
|
||||
|
||||
/// Similar to [`as_ref`](#method.as_ref), and also consumes this `Py` and registers the
|
||||
/// Borrows a GIL-bound reference to the contained `T` independently of the lifetime of `T`.
|
||||
///
|
||||
/// This method is similar to [`as_ref`](#method.as_ref) but consumes `self` and registers the
|
||||
/// Python object reference in PyO3's object storage. The reference count for the Python
|
||||
/// object will not be decreased until the GIL lifetime ends.
|
||||
///
|
||||
/// You should prefer using [`as_ref`](#method.as_ref) if you can as it'll have less overhead.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Useful when returning GIL-bound references from functions. In the snippet below, note that
|
||||
/// the `'py` lifetime of the input GIL lifetime is also given to the returned reference:
|
||||
/// ```
|
||||
/// # use pyo3::prelude::*;
|
||||
/// fn new_py_any<'py>(py: Python<'py>, value: impl IntoPy<PyObject>) -> &'py PyAny {
|
||||
/// let obj: PyObject = value.into_py(py);
|
||||
/// [`Py::as_ref`]'s lifetime limitation forbids creating a function that references a
|
||||
/// variable created inside the function.
|
||||
///
|
||||
/// // .as_ref(py) would not be suitable here, because a reference to `obj` may not be
|
||||
/// // returned from the function.
|
||||
/// ```rust,compile_fail
|
||||
/// # use pyo3::prelude::*;
|
||||
/// #
|
||||
/// fn new_py_any<'py>(py: Python<'py>, value: impl IntoPy<Py<PyAny>>) -> &'py PyAny {
|
||||
/// let obj: Py<PyAny> = value.into_py(py);
|
||||
///
|
||||
/// // The lifetime of the return value of this function is the shortest
|
||||
/// // of `obj` and `py`. As `obj` is owned by the current function,
|
||||
/// // Rust won't let the return value escape this function!
|
||||
/// obj.as_ref(py)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This can be solved by using [`Py::into_ref`] instead, which does not suffer from this issue.
|
||||
/// Note that the lifetime of the [`Python<'py>`](crate::Python) token is transferred to
|
||||
/// the returned reference.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use pyo3::prelude::*;
|
||||
/// #
|
||||
/// fn new_py_any<'py>(py: Python<'py>, value: impl IntoPy<Py<PyAny>>) -> &'py PyAny {
|
||||
/// let obj: Py<PyAny> = value.into_py(py);
|
||||
///
|
||||
/// // This reference's lifetime is determined by `py`'s lifetime.
|
||||
/// // Because that originates from outside this function,
|
||||
/// // this return value is allowed.
|
||||
/// obj.into_ref(py)
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -132,22 +309,72 @@ impl<T> Py<T>
|
|||
where
|
||||
T: PyClass,
|
||||
{
|
||||
/// Immutably borrows the value `T`. This borrow lasts untill the returned `PyRef` exists.
|
||||
/// Immutably borrows the value `T`.
|
||||
///
|
||||
/// This borrow lasts while the returned [`PyRef`] exists.
|
||||
/// Multiple immutable borrows can be taken out at the same time.
|
||||
///
|
||||
/// Equivalent to `self.as_ref(py).borrow()` -
|
||||
/// see [`PyCell::borrow`](../pycell/struct.PyCell.html#method.borrow)
|
||||
/// see [`PyCell::borrow`](crate::pycell::PyCell::borrow).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use pyo3::prelude::*;
|
||||
/// #
|
||||
/// #[pyclass]
|
||||
/// struct Foo {
|
||||
/// inner: u8,
|
||||
/// }
|
||||
///
|
||||
/// # fn main() -> PyResult<()> {
|
||||
/// Python::with_gil(|py| -> PyResult<()> {
|
||||
/// let foo: Py<Foo> = Py::new(py, Foo { inner: 73 })?;
|
||||
/// let inner: &u8 = &foo.borrow(py).inner;
|
||||
///
|
||||
/// assert_eq!(*inner, 73);
|
||||
/// Ok(())
|
||||
/// })?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the value is currently mutably borrowed. For a non-panicking variant, use
|
||||
/// [`try_borrow`](#method.try_borrow).
|
||||
pub fn borrow<'py>(&'py self, py: Python<'py>) -> PyRef<'py, T> {
|
||||
self.as_ref(py).borrow()
|
||||
}
|
||||
|
||||
/// Mutably borrows the value `T`. This borrow lasts untill the returned `PyRefMut` exists.
|
||||
/// Mutably borrows the value `T`.
|
||||
///
|
||||
/// This borrow lasts while the returned [`PyRefMut`] exists.
|
||||
///
|
||||
/// Equivalent to `self.as_ref(py).borrow_mut()` -
|
||||
/// see [`PyCell::borrow_mut`](../pycell/struct.PyCell.html#method.borrow_mut)
|
||||
/// see [`PyCell::borrow_mut`](crate::pycell::PyCell::borrow_mut).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pyo3::prelude::*;
|
||||
/// #
|
||||
/// #[pyclass]
|
||||
/// struct Foo {
|
||||
/// inner: u8,
|
||||
/// }
|
||||
///
|
||||
/// # fn main() -> PyResult<()> {
|
||||
/// Python::with_gil(|py| -> PyResult<()> {
|
||||
/// let foo: Py<Foo> = Py::new(py, Foo { inner: 73 })?;
|
||||
/// foo.borrow_mut(py).inner = 35;
|
||||
///
|
||||
/// assert_eq!(foo.borrow(py).inner, 35);
|
||||
/// Ok(())
|
||||
/// })?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the value is currently mutably borrowed. For a non-panicking variant, use
|
||||
|
@ -156,24 +383,26 @@ where
|
|||
self.as_ref(py).borrow_mut()
|
||||
}
|
||||
|
||||
/// Immutably borrows the value `T`, returning an error if the value is currently
|
||||
/// mutably borrowed. This borrow lasts untill the returned `PyRef` exists.
|
||||
/// Attempts to immutably borrow the value `T`, returning an error if the value is currently mutably borrowed.
|
||||
///
|
||||
/// The borrow lasts while the returned [`PyRef`] exists.
|
||||
///
|
||||
/// This is the non-panicking variant of [`borrow`](#method.borrow).
|
||||
///
|
||||
/// Equivalent to `self.as_ref(py).try_borrow()` -
|
||||
/// see [`PyCell::try_borrow`](../pycell/struct.PyCell.html#method.try_borrow)
|
||||
/// Equivalent to `self.as_ref(py).borrow_mut()` -
|
||||
/// see [`PyCell::try_borrow`](crate::pycell::PyCell::try_borrow).
|
||||
pub fn try_borrow<'py>(&'py self, py: Python<'py>) -> Result<PyRef<'py, T>, PyBorrowError> {
|
||||
self.as_ref(py).try_borrow()
|
||||
}
|
||||
|
||||
/// Mutably borrows the value `T`, returning an error if the value is currently borrowed.
|
||||
/// This borrow lasts untill the returned `PyRefMut` exists.
|
||||
/// Attempts to mutably borrow the value `T`, returning an error if the value is currently borrowed.
|
||||
///
|
||||
/// The borrow lasts while the returned [`PyRefMut`] exists.
|
||||
///
|
||||
/// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut).
|
||||
///
|
||||
/// Equivalent to `self.as_ref(py).try_borrow_mut() -
|
||||
/// see [`PyCell::try_borrow_mut`](../pycell/struct.PyCell.html#method.try_borrow_mut)
|
||||
/// Equivalent to `self.as_ref(py).try_borrow_mut()` -
|
||||
/// see [`PyCell::try_borrow_mut`](crate::pycell::PyCell::try_borrow_mut).
|
||||
pub fn try_borrow_mut<'py>(
|
||||
&'py self,
|
||||
py: Python<'py>,
|
||||
|
@ -189,7 +418,29 @@ impl<T> Py<T> {
|
|||
unsafe { ffi::Py_REFCNT(self.0.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Clones self by calling `Py_INCREF()` on the ptr.
|
||||
/// Makes a clone of `self`.
|
||||
///
|
||||
/// This creates another pointer to the same object, increasing its reference count.
|
||||
///
|
||||
/// You should prefer using this method over [`Clone`] if you happen to be holding the GIL already.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use pyo3::prelude::*;
|
||||
/// use pyo3::types::PyDict;
|
||||
/// use pyo3::conversion::AsPyPointer;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// Python::with_gil(|py| {
|
||||
/// let first: Py<PyDict> = PyDict::new(py).into();
|
||||
/// let second = Py::clone_ref(&first, py);
|
||||
///
|
||||
/// // Both point to the same object
|
||||
/// assert_eq!(first.as_ptr(), second.as_ptr());
|
||||
/// });
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn clone_ref(&self, py: Python) -> Py<T> {
|
||||
unsafe { Py::from_borrowed_ptr(py, self.0.as_ptr()) }
|
||||
|
@ -524,6 +775,9 @@ impl<T> PartialEq for Py<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// If the GIL is held this increments `self`'s reference count.
|
||||
/// Otherwise this registers the [`Py`]`<T>` instance to have its reference count
|
||||
/// incremented the next time PyO3 acquires the GIL.
|
||||
impl<T> Clone for Py<T> {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
|
@ -608,7 +862,7 @@ impl PyObject {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use super::{Py, PyObject};
|
||||
use crate::types::PyDict;
|
||||
use crate::{ffi, AsPyPointer, Python};
|
||||
|
|
232
src/lib.rs
232
src/lib.rs
|
@ -1,44 +1,139 @@
|
|||
#![cfg_attr(feature = "nightly", feature(specialization))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![allow(clippy::missing_safety_doc)] // FIXME (#698)
|
||||
|
||||
//! Rust bindings to the Python interpreter.
|
||||
//!
|
||||
//! Look at [the guide](https://pyo3.rs/) for a detailed introduction.
|
||||
//! PyO3 can be used to write native Python modules or run Python code and modules from Rust.
|
||||
//!
|
||||
//! # Ownership and Lifetimes
|
||||
//! See [the guide](https://pyo3.rs/) for a detailed introduction.
|
||||
//!
|
||||
//! Because all Python objects potentially have multiple owners, the concept of
|
||||
//! Rust mutability does not apply to Python objects. As a result, PyO3 allows
|
||||
//! mutating Python objects even if they are not stored in a mutable Rust
|
||||
//! variable.
|
||||
//! # PyO3's object types
|
||||
//!
|
||||
//! In Python, all objects are implicitly reference counted. The Python
|
||||
//! interpreter uses a global interpreter lock (GIL) to ensure thread-safety.
|
||||
//! Thus, we use `struct Python<'py>` as a token to indicate that
|
||||
//! a function can assume that the GIL is held. In Rust, we use different types
|
||||
//! to represent a reference to a Python object, depending on whether we know
|
||||
//! the GIL is held, and depending on whether we know the underlying type. See
|
||||
//! [the guide](https://pyo3.rs/main/types.html) for an explanation of
|
||||
//! the different Python object types.
|
||||
//! PyO3 has several core types that you should familiarize yourself with:
|
||||
//!
|
||||
//! A `Python` instance is either obtained explicitly by acquiring the GIL,
|
||||
//! or implicitly by PyO3 when it generates the wrapper code for Rust functions
|
||||
//! and structs wrapped as Python functions and objects.
|
||||
//! ## The Python<'py> object
|
||||
//!
|
||||
//! # Error Handling
|
||||
//! Holding the [global interpreter lock](https://docs.python.org/3/glossary.html#term-global-interpreter-lock)
|
||||
//! (GIL) is modeled with the [`Python<'py>`](crate::Python) token.
|
||||
//! All APIs that require that the GIL is held require this token as proof
|
||||
//! that you really are holding the GIL. It can be explicitly acquired and
|
||||
//! is also implicitly acquired by PyO3 as it wraps Rust functions and structs
|
||||
//! into Python functions and objects.
|
||||
//!
|
||||
//! The vast majority of operations in this library will return `PyResult<...>`.
|
||||
//! ## The GIL-dependent types
|
||||
//!
|
||||
//! For example `&`[`PyAny`](crate::types::PyAny).
|
||||
//! These are only ever seen as references, with a lifetime that is only valid for as long
|
||||
//! as the GIL is held, which is why using them doesn't require a [`Python<'py>`](crate::Python) token.
|
||||
//! The underlying Python object, if mutable, can be mutated through any reference.
|
||||
//!
|
||||
//! See the [guide](https://pyo3.rs/latest/types.html) for an explanation of the different Python object types.
|
||||
//!
|
||||
//! ## The GIL-independent types
|
||||
//!
|
||||
//! When wrapped in [`Py`]`<...>`, like with [`Py`]`<`[`PyAny`](crate::types::PyAny)`>` or [`Py`]`<SomePyClass>`, Python objects
|
||||
//! no longer have a limited lifetime which makes them easier to store in structs and pass between functions.
|
||||
//! However, you cannot do much with them without a
|
||||
//! [`Python<'py>`](crate::Python) token, for which you’d need to reacquire the GIL.
|
||||
//!
|
||||
//! ## PyErr
|
||||
//!
|
||||
//! The vast majority of operations in this library will return [`PyResult<...>`](PyResult).
|
||||
//! This is an alias for the type `Result<..., PyErr>`.
|
||||
//!
|
||||
//! A `PyErr` represents a Python exception. Errors within the `PyO3` library are
|
||||
//! also exposed as Python exceptions.
|
||||
//! A `PyErr` represents a Python exception. A `PyErr` returned to Python code will be raised as a Python exception.
|
||||
//! Errors from `PyO3` itself are also exposed as Python exceptions.
|
||||
//!
|
||||
//! # Examples
|
||||
//! # Feature flags
|
||||
//!
|
||||
//! ## Using Rust from Python
|
||||
//! PyO3 uses [feature flags](https://doc.rust-lang.org/cargo/reference/features.html)
|
||||
//! to enable you to opt-in to additional functionality. For a detailed description, see
|
||||
//! the [Features Reference chapter of the guide](https://pyo3.rs/latest/features.html#features-reference).
|
||||
//!
|
||||
//! PyO3 can be used to generate a native Python module.
|
||||
//! ## Default feature flags
|
||||
//!
|
||||
//! The following features are turned on by default:
|
||||
//! - `macros`: Enables various macros, including all the attribute macros.
|
||||
//!
|
||||
//! ## Optional feature flags
|
||||
//!
|
||||
//! The following features are optional:
|
||||
//! - `abi3`: Restricts PyO3's API to a subset of the full Python API which is guaranteed
|
||||
//! by [PEP 384](https://www.python.org/dev/peps/pep-0384/) to be forward-compatible with future Python versions.
|
||||
//
|
||||
//! - `auto-initialize`: Changes [`Python::with_gil`](crate::Python::with_gil) and
|
||||
//! [`Python::acquire_gil`](crate::Python::acquire_gil) to automatically initialize the
|
||||
//! Python interpreter if needed.
|
||||
//
|
||||
//! - `extension-module`: This will tell the linker to keep the Python symbols unresolved,
|
||||
//! so that your module can also be used with statically linked Python interpreters.
|
||||
//! Use this feature when building an extension module.
|
||||
//
|
||||
//! - `hashbrown`: Enables conversions between Python objects and
|
||||
//! [hashbrown](https://docs.rs/hashbrown)'s
|
||||
//! [`HashMap`](https://docs.rs/hashbrown/latest/hashbrown/struct.HashMap.html) and
|
||||
//! [`HashSet`](https://docs.rs/hashbrown/latest/hashbrown/struct.HashSet.html) types.
|
||||
//
|
||||
//! - [`indexmap`](crate::indexmap): Enables conversions between Python dictionary and
|
||||
//! [indexmap](https://docs.rs/indexmap)'s
|
||||
//! [`IndexMap`](https://docs.rs/indexmap/latest/indexmap/map/struct.IndexMap.html).
|
||||
//
|
||||
//! - `multiple-pymethods`: Enables the use of multiple
|
||||
//! [`#[pymethods]`](crate::proc_macro::pymethods) blocks per
|
||||
//! [`#[pyclass]`](crate::proc_macro::pyclass). This adds a dependency on the
|
||||
//! [`inventory`](https://docs.rs/inventory) crate, which is not supported on all platforms.
|
||||
//
|
||||
//! - [`num-bigint`](./num_bigint/index.html): Enables conversions between Python objects and
|
||||
//! [num-bigint](https://docs.rs/num-bigint)'s
|
||||
//! [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html) and
|
||||
//! [`BigUint`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigUint.html) types.
|
||||
//
|
||||
//! - [`num-complex`](crate::num_complex): Enables conversions between Python objects and
|
||||
//! [num-complex](https://docs.rs/num-complex)'s
|
||||
//! [`Complex`](https://docs.rs/num-complex/latest/num_complex/struct.Complex.html) type.
|
||||
//
|
||||
//! - `serde`: Allows implementing [serde](https://docs.rs/serde)'s
|
||||
//! [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html) and
|
||||
//! [`Deserialize`](https://docs.rs/serde/latest/serde/trait.Deserialize.html) traits for
|
||||
//! [`Py`]`<T>` for all `T` that implement
|
||||
//! [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html) and
|
||||
//! [`Deserialize`](https://docs.rs/serde/latest/serde/trait.Deserialize.html).
|
||||
//!
|
||||
//! ## Unstable features
|
||||
//!
|
||||
//! - `nightly`: Gates some optimizations that rely on
|
||||
//! [`#![feature(specialization)]`](https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md),
|
||||
//! for which you'd also need nightly Rust. You should not use this feature.
|
||||
//
|
||||
//! ## `rustc` environment flags
|
||||
//!
|
||||
//! PyO3 uses `rustc`'s `--cfg` flags to enable or disable code used for different Python versions.
|
||||
//! If you want to do this for your own crate, you can do so with the [`pyo3-build-config`](https://docs.rs/pyo3-build-config) crate.
|
||||
//!
|
||||
//! - `Py_3_6`, `Py_3_7`, `Py_3_8`, `Py_3_9`, `Py_3_10`: Marks code that is only enabled when compiling for a given minimum Python version.
|
||||
//
|
||||
//! - `Py_LIMITED_API`: Marks code enabled when the `abi3` feature flag is enabled.
|
||||
//
|
||||
//! - `PyPy` - Marks code enabled when compiling for PyPy.
|
||||
//!
|
||||
//! # Minimum supported Rust and Python versions
|
||||
//!
|
||||
//! PyO3 supports Python 3.6+ and Rust 1.41+.
|
||||
//!
|
||||
//! Building with PyPy is also possible (via cpyext) for Python 3.6,
|
||||
//! targeted PyPy version is 7.3+. Please refer to the
|
||||
//! [pypy section](https://pyo3.rs/latest/building_and_distribution/pypy.html)
|
||||
//! in the guide for more information.
|
||||
//!
|
||||
//! # Example: Building a native Python module
|
||||
//!
|
||||
//! To build, test and publish your crate as a Python module, it is recommended that you use
|
||||
//! [maturin](https://github.com/PyO3/maturin) or
|
||||
//! [setuptools-rust](https://github.com/PyO3/setuptools-rust). You can also do this manually. See the
|
||||
//! [Building and Distribution chapter of the guide](https://pyo3.rs/latest/building_and_distribution.html)
|
||||
//! for more information.
|
||||
//!
|
||||
//! Add these files to your crate's root:
|
||||
//!
|
||||
//! **`Cargo.toml`**
|
||||
//!
|
||||
|
@ -58,7 +153,9 @@
|
|||
//! crate-type = ["cdylib"]
|
||||
//!
|
||||
//! [dependencies.pyo3]
|
||||
//! version = "0.13.2"
|
||||
// workaround for `extended_key_value_attributes`: https://github.com/rust-lang/rust/issues/82768#issuecomment-803935643
|
||||
#![cfg_attr(docsrs, cfg_attr(docsrs, doc = concat!("version = \"", env!("CARGO_PKG_VERSION"), "\"")))]
|
||||
#![cfg_attr(not(docsrs), doc = "version = \"*\"")]
|
||||
//! features = ["extension-module"]
|
||||
//! ```
|
||||
//!
|
||||
|
@ -66,16 +163,15 @@
|
|||
//!
|
||||
//! ```rust
|
||||
//! use pyo3::prelude::*;
|
||||
//! use pyo3::wrap_pyfunction;
|
||||
//!
|
||||
//! #[pyfunction]
|
||||
//! /// Formats the sum of two numbers as string.
|
||||
//! #[pyfunction]
|
||||
//! fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
|
||||
//! Ok((a + b).to_string())
|
||||
//! }
|
||||
//!
|
||||
//! #[pymodule]
|
||||
//! /// A Python module implemented in Rust.
|
||||
//! #[pymodule]
|
||||
//! fn string_sum(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
//! m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
|
||||
//!
|
||||
|
@ -83,25 +179,37 @@
|
|||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! While developing, you symlink (or copy) and rename the shared library from
|
||||
//! the target folder: On macOS, rename `libstring_sum.dylib` to
|
||||
//! `string_sum.so`, on Windows `libstring_sum.dll` to `string_sum.pyd` and on
|
||||
//! Linux `libstring_sum.so` to `string_sum.so`. Then open a Python shell in the
|
||||
//! same folder and you'll be able to `import string_sum`.
|
||||
//! **`.cargo/config.toml`**
|
||||
//! ```toml
|
||||
//! # These flags must be passed to rustc when compiling for macOS
|
||||
//! # They can be omitted if you pass the flags yourself
|
||||
//! # or don't care about macOS
|
||||
//!
|
||||
//! To build, test and publish your crate as a Python module, you can use
|
||||
//! [maturin](https://github.com/PyO3/maturin) or
|
||||
//! [setuptools-rust](https://github.com/PyO3/setuptools-rust). You can find an
|
||||
//! example for setuptools-rust in [examples/word-count](examples/word-count),
|
||||
//! while maturin should work on your crate without any configuration.
|
||||
//! [target.x86_64-apple-darwin]
|
||||
//! rustflags = [
|
||||
//! "-C", "link-arg=-undefined",
|
||||
//! "-C", "link-arg=dynamic_lookup",
|
||||
//! ]
|
||||
//!
|
||||
//! ## Using Python from Rust
|
||||
//! [target.aarch64-apple-darwin]
|
||||
//! rustflags = [
|
||||
//! "-C", "link-arg=-undefined",
|
||||
//! "-C", "link-arg=dynamic_lookup",
|
||||
//! ]
|
||||
//! ```
|
||||
//!
|
||||
//! # Example: Using Python from Rust
|
||||
//!
|
||||
//! You can use PyO3 to call Python functions from Rust.
|
||||
//!
|
||||
//! Add `pyo3` to your `Cargo.toml`:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies.pyo3]
|
||||
//! version = "0.13.2"
|
||||
// workaround for `extended_key_value_attributes`: https://github.com/rust-lang/rust/issues/82768#issuecomment-803935643
|
||||
#![cfg_attr(docsrs, cfg_attr(docsrs, doc = concat!("version = \"", env!("CARGO_PKG_VERSION"), "\"")))]
|
||||
#![cfg_attr(not(docsrs), doc = "version = \"*\"")]
|
||||
//! # this is necessary to automatically initialize the Python interpreter
|
||||
//! features = ["auto-initialize"]
|
||||
//! ```
|
||||
//!
|
||||
|
@ -175,7 +283,6 @@ pub mod derive_utils;
|
|||
mod err;
|
||||
pub mod exceptions;
|
||||
pub mod ffi;
|
||||
pub mod freelist;
|
||||
mod gil;
|
||||
pub mod impl_;
|
||||
mod instance;
|
||||
|
@ -192,15 +299,26 @@ pub mod pyclass;
|
|||
pub mod pyclass_init;
|
||||
pub mod pyclass_slots;
|
||||
mod python;
|
||||
|
||||
pub mod type_object;
|
||||
pub mod types;
|
||||
|
||||
pub mod num_bigint;
|
||||
|
||||
pub mod num_complex;
|
||||
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "indexmap")))]
|
||||
#[cfg(feature = "indexmap")]
|
||||
pub use crate::conversions::indexmap;
|
||||
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod serde;
|
||||
|
||||
/// The proc macros, all of which are part of the prelude.
|
||||
///
|
||||
/// Import these with `use pyo3::prelude::*;`
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
||||
#[cfg(feature = "macros")]
|
||||
pub mod proc_macro {
|
||||
pub use pyo3_macros::pymodule;
|
||||
|
@ -230,35 +348,6 @@ macro_rules! wrap_pyfunction {
|
|||
};
|
||||
}
|
||||
|
||||
/// Returns the function that is called in the C-FFI.
|
||||
///
|
||||
/// Use this together with `#[pyfunction]` and [types::PyCFunction].
|
||||
/// ```
|
||||
/// use pyo3::prelude::*;
|
||||
/// use pyo3::types::PyCFunction;
|
||||
/// use pyo3::raw_pycfunction;
|
||||
///
|
||||
/// #[pyfunction]
|
||||
/// fn some_fun(arg: i32) -> PyResult<()> {
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// #[pymodule]
|
||||
/// fn module(_py: Python, module: &PyModule) -> PyResult<()> {
|
||||
/// let ffi_wrapper_fun = raw_pycfunction!(some_fun);
|
||||
/// let docs = "Some documentation string with null-termination\0";
|
||||
/// let py_cfunction =
|
||||
/// PyCFunction::new_with_keywords(ffi_wrapper_fun, "function_name", docs, module.into())?;
|
||||
/// module.add_function(py_cfunction)
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! raw_pycfunction {
|
||||
($function_name: ident) => {{
|
||||
pyo3::paste::expr! { [<__pyo3_raw_ $function_name>] }
|
||||
}};
|
||||
}
|
||||
|
||||
/// Returns a function that takes a [Python] instance and returns a Python module.
|
||||
///
|
||||
/// Use this together with `#[pymodule]` and [types::PyModule::add_wrapped].
|
||||
|
@ -431,4 +520,5 @@ pub mod doc_test {
|
|||
doctest!("guide/src/rust_cpython.md", guide_rust_cpython_md);
|
||||
doctest!("guide/src/trait_bounds.md", guide_trait_bounds_md);
|
||||
doctest!("guide/src/types.md", guide_types_md);
|
||||
doctest!("guide/src/faq.md", faq);
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ where
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::PyDict;
|
||||
|
||||
|
|
|
@ -0,0 +1,295 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
//
|
||||
// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython
|
||||
|
||||
#![cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy))))]
|
||||
#![cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy)))))
|
||||
)]
|
||||
//! Conversions to and from [num-bigint](https://docs.rs/num-bigint)’s [`BigInt`] and [`BigUint`] types.
|
||||
//!
|
||||
//! This is useful for converting Python integers when they may not fit in Rust's built-in integer types.
|
||||
//!
|
||||
//! # Setup
|
||||
//!
|
||||
//! To use this feature, add this to your **`Cargo.toml`**:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! num-bigint = "*"
|
||||
// workaround for `extended_key_value_attributes`: https://github.com/rust-lang/rust/issues/82768#issuecomment-803935643
|
||||
#![cfg_attr(docsrs, cfg_attr(docsrs, doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"num-bigint\"] }")))]
|
||||
#![cfg_attr(
|
||||
not(docsrs),
|
||||
doc = "pyo3 = { version = \"*\", features = [\"num-bigint\"] }"
|
||||
)]
|
||||
//! ```
|
||||
//!
|
||||
//! Note that you must use compatible versions of num-bigint and PyO3.
|
||||
//! The required num-bigint version may vary based on the version of PyO3.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! Using [`BigInt`] to correctly increment an arbitrary precision integer.
|
||||
//! This is not possible with Rust's native integers if the Python integer is too large,
|
||||
//! in which case it will fail its conversion and raise `OverflowError`.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use num_bigint::BigInt;
|
||||
//! use pyo3::prelude::*;
|
||||
//!
|
||||
//! #[pyfunction]
|
||||
//! fn add_one(n: BigInt) -> BigInt {
|
||||
//! n + 1
|
||||
//! }
|
||||
//!
|
||||
//! #[pymodule]
|
||||
//! fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
//! m.add_function(wrap_pyfunction!(add_one, m)?)?;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Python code:
|
||||
//! ```python
|
||||
//! from my_module import add_one
|
||||
//!
|
||||
//! n = 1 << 1337
|
||||
//! value = add_one(n)
|
||||
//!
|
||||
//! assert n + 1 == value
|
||||
//! ```
|
||||
|
||||
use crate::{
|
||||
err, ffi, types::*, AsPyPointer, FromPyObject, IntoPy, Py, PyAny, PyErr, PyNativeType,
|
||||
PyObject, PyResult, Python, ToPyObject,
|
||||
};
|
||||
|
||||
use num_bigint::{BigInt, BigUint};
|
||||
use std::os::raw::{c_int, c_uchar};
|
||||
|
||||
#[cfg(not(all(windows, PyPy)))]
|
||||
unsafe fn extract(ob: &PyLong, buffer: &mut [c_uchar], is_signed: c_int) -> PyResult<()> {
|
||||
err::error_on_minusone(
|
||||
ob.py(),
|
||||
ffi::_PyLong_AsByteArray(
|
||||
ob.as_ptr() as *mut ffi::PyLongObject,
|
||||
buffer.as_mut_ptr(),
|
||||
buffer.len(),
|
||||
1,
|
||||
is_signed,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! bigint_conversion {
|
||||
($rust_ty: ty, $is_signed: expr, $to_bytes: path, $from_bytes: path) => {
|
||||
impl ToPyObject for $rust_ty {
|
||||
fn to_object(&self, py: Python) -> PyObject {
|
||||
unsafe {
|
||||
let bytes = $to_bytes(self);
|
||||
let obj = ffi::_PyLong_FromByteArray(
|
||||
bytes.as_ptr() as *const c_uchar,
|
||||
bytes.len(),
|
||||
1,
|
||||
$is_signed,
|
||||
);
|
||||
PyObject::from_owned_ptr(py, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl IntoPy<PyObject> for $rust_ty {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
self.to_object(py)
|
||||
}
|
||||
}
|
||||
impl<'source> FromPyObject<'source> for $rust_ty {
|
||||
fn extract(ob: &'source PyAny) -> PyResult<$rust_ty> {
|
||||
let py = ob.py();
|
||||
unsafe {
|
||||
let num = ffi::PyNumber_Index(ob.as_ptr());
|
||||
if num.is_null() {
|
||||
return Err(PyErr::fetch(py));
|
||||
}
|
||||
let n_bits = ffi::_PyLong_NumBits(num);
|
||||
let n_bytes = if n_bits < 0 {
|
||||
return Err(PyErr::fetch(py));
|
||||
} else if n_bits == 0 {
|
||||
0
|
||||
} else {
|
||||
(n_bits as usize - 1 + $is_signed) / 8 + 1
|
||||
};
|
||||
let num: Py<PyLong> = Py::from_owned_ptr(py, num);
|
||||
if n_bytes <= 128 {
|
||||
let mut buffer = [0; 128];
|
||||
extract(num.as_ref(py), &mut buffer[..n_bytes], $is_signed)?;
|
||||
Ok($from_bytes(&buffer[..n_bytes]))
|
||||
} else {
|
||||
let mut buffer = vec![0; n_bytes];
|
||||
extract(num.as_ref(py), &mut buffer, $is_signed)?;
|
||||
Ok($from_bytes(&buffer))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
bigint_conversion!(BigUint, 0, BigUint::to_bytes_le, BigUint::from_bytes_le);
|
||||
bigint_conversion!(
|
||||
BigInt,
|
||||
1,
|
||||
BigInt::to_signed_bytes_le,
|
||||
BigInt::from_signed_bytes_le
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::{PyDict, PyModule};
|
||||
use indoc::indoc;
|
||||
|
||||
fn python_fib(py: Python) -> &PyModule {
|
||||
let fib_code = indoc!(
|
||||
r#"
|
||||
def fib(n):
|
||||
f0, f1 = 0, 1
|
||||
for _ in range(n):
|
||||
f0, f1 = f1, f0 + f1
|
||||
return f0
|
||||
|
||||
def fib_neg(n):
|
||||
return -fib(n)
|
||||
"#
|
||||
);
|
||||
PyModule::from_code(py, fib_code, "fib.py", "fib").unwrap()
|
||||
}
|
||||
|
||||
fn rust_fib<T>(n: usize) -> T
|
||||
where
|
||||
T: From<u16>,
|
||||
for<'a> &'a T: std::ops::Add<Output = T>,
|
||||
{
|
||||
let mut f0: T = T::from(0);
|
||||
let mut f1: T = T::from(1);
|
||||
for _ in 0..n {
|
||||
let f2 = &f0 + &f1;
|
||||
f0 = std::mem::replace(&mut f1, f2);
|
||||
}
|
||||
f0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_biguint() {
|
||||
Python::with_gil(|py| {
|
||||
let rs_result: BigUint = rust_fib(400);
|
||||
let fib = python_fib(py);
|
||||
let locals = PyDict::new(py);
|
||||
locals.set_item("rs_result", &rs_result).unwrap();
|
||||
locals.set_item("fib", fib).unwrap();
|
||||
// Checks if Rust BigUint -> Python Long conversion is correct
|
||||
py.run("assert fib.fib(400) == rs_result", None, Some(locals))
|
||||
.unwrap();
|
||||
// Checks if Python Long -> Rust BigUint conversion is correct if N is small
|
||||
let py_result: BigUint =
|
||||
FromPyObject::extract(fib.getattr("fib").unwrap().call1((400,)).unwrap()).unwrap();
|
||||
assert_eq!(rs_result, py_result);
|
||||
// Checks if Python Long -> Rust BigUint conversion is correct if N is large
|
||||
let rs_result: BigUint = rust_fib(2000);
|
||||
let py_result: BigUint =
|
||||
FromPyObject::extract(fib.getattr("fib").unwrap().call1((2000,)).unwrap()).unwrap();
|
||||
assert_eq!(rs_result, py_result);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_bigint() {
|
||||
Python::with_gil(|py| {
|
||||
let rs_result = rust_fib::<BigInt>(400) * -1;
|
||||
let fib = python_fib(py);
|
||||
let locals = PyDict::new(py);
|
||||
locals.set_item("rs_result", &rs_result).unwrap();
|
||||
locals.set_item("fib", fib).unwrap();
|
||||
// Checks if Rust BigInt -> Python Long conversion is correct
|
||||
py.run("assert fib.fib_neg(400) == rs_result", None, Some(locals))
|
||||
.unwrap();
|
||||
// Checks if Python Long -> Rust BigInt conversion is correct if N is small
|
||||
let py_result: BigInt =
|
||||
FromPyObject::extract(fib.getattr("fib_neg").unwrap().call1((400,)).unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(rs_result, py_result);
|
||||
// Checks if Python Long -> Rust BigInt conversion is correct if N is large
|
||||
let rs_result = rust_fib::<BigInt>(2000) * -1;
|
||||
let py_result: BigInt =
|
||||
FromPyObject::extract(fib.getattr("fib_neg").unwrap().call1((2000,)).unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(rs_result, py_result);
|
||||
})
|
||||
}
|
||||
|
||||
fn python_index_class(py: Python) -> &PyModule {
|
||||
let index_code = indoc!(
|
||||
r#"
|
||||
class C:
|
||||
def __init__(self, x):
|
||||
self.x = x
|
||||
def __index__(self):
|
||||
return self.x
|
||||
"#
|
||||
);
|
||||
PyModule::from_code(py, index_code, "index.py", "index").unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_index_class() {
|
||||
Python::with_gil(|py| {
|
||||
let index = python_index_class(py);
|
||||
let locals = PyDict::new(py);
|
||||
locals.set_item("index", index).unwrap();
|
||||
let ob = py.eval("index.C(10)", None, Some(locals)).unwrap();
|
||||
let _: BigInt = FromPyObject::extract(ob).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handle_zero() {
|
||||
Python::with_gil(|py| {
|
||||
let fib = python_fib(py);
|
||||
let zero: BigInt =
|
||||
FromPyObject::extract(fib.getattr("fib").unwrap().call1((0,)).unwrap()).unwrap();
|
||||
assert_eq!(zero, BigInt::from(0));
|
||||
})
|
||||
}
|
||||
|
||||
/// `OverflowError` on converting Python int to BigInt, see issue #629
|
||||
#[test]
|
||||
fn check_overflow() {
|
||||
Python::with_gil(|py| {
|
||||
macro_rules! test {
|
||||
($T:ty, $value:expr, $py:expr) => {
|
||||
let value = $value;
|
||||
println!("{}: {}", stringify!($T), value);
|
||||
let python_value = value.clone().to_object(py);
|
||||
let roundtrip_value = python_value.extract::<$T>(py).unwrap();
|
||||
assert_eq!(value, roundtrip_value);
|
||||
};
|
||||
}
|
||||
|
||||
for i in 0..=256usize {
|
||||
// test a lot of values to help catch other bugs too
|
||||
test!(BigInt, BigInt::from(i), py);
|
||||
test!(BigUint, BigUint::from(i), py);
|
||||
test!(BigInt, -BigInt::from(i), py);
|
||||
test!(BigInt, BigInt::from(1) << i, py);
|
||||
test!(BigUint, BigUint::from(1u32) << i, py);
|
||||
test!(BigInt, -BigInt::from(1) << i, py);
|
||||
test!(BigInt, (BigInt::from(1) << i) + 1u32, py);
|
||||
test!(BigUint, (BigUint::from(1u32) << i) + 1u32, py);
|
||||
test!(BigInt, (-BigInt::from(1) << i) + 1u32, py);
|
||||
test!(BigInt, (BigInt::from(1) << i) - 1u32, py);
|
||||
test!(BigUint, (BigUint::from(1u32) << i) - 1u32, py);
|
||||
test!(BigInt, (-BigInt::from(1) << i) - 1u32, py);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue