Compare commits
69 Commits
master
...
release-0.
Author | SHA1 | Date |
---|---|---|
Paul Stemmet | 338c71d0ad | |
David Hewitt | a22e4f6005 | |
David Hewitt | 5f65a97bdc | |
David Hewitt | 2d5a54e4d2 | |
David Hewitt | 3447f0e77d | |
David Hewitt | 5157a45b39 | |
Adam Reichold | f7afb8157d | |
David Hewitt | 4774ded49a | |
David Hewitt | bcef18b988 | |
Adam Reichold | fa6d60b77e | |
Adam Reichold | f9f0bdde70 | |
David Hewitt | cf213252fa | |
David Hewitt | f7893858d2 | |
Adam Reichold | 9120b35f35 | |
Adam Reichold | 2e79c557cc | |
Adam Reichold | 2564ca4e75 | |
David Hewitt | be4d5627a3 | |
David Hewitt | d3f034a80f | |
David Hewitt | 985412fb8f | |
Adam Reichold | ecb0e9cb61 | |
Alex Gaynor | b84271140e | |
Adam Reichold | d897479831 | |
Nathan Kent | 8f6976d9a5 | |
David Hewitt | 5c1e4d10b3 | |
David Hewitt | 1896a32015 | |
messense | 7032789daf | |
Alex Gaynor | 1166a995a4 | |
David Hewitt | 856b859efe | |
David Hewitt | 413dda09f5 | |
David Hewitt | 485f5c00e2 | |
Alex Gaynor | 830b3bb814 | |
mejrs | 405d722a2d | |
David Hewitt | b1de927a31 | |
Ivan Smirnov | 2312270ec1 | |
David Hewitt | e0513d74f5 | |
David Hewitt | 25b8a37521 | |
Joseph Perez | 3d17f7442a | |
Joseph Perez | 466359a1c8 | |
David Hewitt | 9dbd81b47c | |
David Hewitt | bbc5404297 | |
David Hewitt | 8d637b0b5b | |
David Hewitt | aa6f1466d2 | |
Surya | c8ef081821 | |
David Hewitt | d649f6603f | |
Joseph Perez | b08c92b306 | |
Joseph Perez | a4aba0a09a | |
Joseph Perez | 04bb9f2110 | |
Samuel Pastva | 10086d176a | |
Samuel Pastva | 15c280015d | |
Samuel Pastva | 2aca7f53f0 | |
David Hewitt | 04af02f155 | |
David Hewitt | 8e08e4ad1b | |
David Hewitt | 7f328767a3 | |
David Hewitt | ba5a1da4a8 | |
David Hewitt | 410ef89456 | |
David Hewitt | 92cde096b5 | |
David Hewitt | 8c272a6ef2 | |
David Hewitt | e900df02f0 | |
dependabot[bot] | 97bf194152 | |
David Hewitt | 422f8665c9 | |
David Hewitt | 30463b6720 | |
David Hewitt | f745299b7b | |
Adam Reichold | d468f570ae | |
Orhun Parmaksız | 826fa973b6 | |
Orhun Parmaksız | 779eb2412c | |
David Hewitt | 84264b358e | |
David Hewitt | 9e07203afb | |
David Hewitt | 8392ed2a94 | |
Orhun Parmaksız | 427b2e9386 |
|
@ -1,20 +0,0 @@
|
||||||
[target.'cfg(feature = "cargo-clippy")']
|
|
||||||
rustflags = [
|
|
||||||
# Lints to enforce in CI
|
|
||||||
"-Dclippy::checked_conversions",
|
|
||||||
"-Dclippy::dbg_macro",
|
|
||||||
"-Dclippy::explicit_into_iter_loop",
|
|
||||||
"-Dclippy::explicit_iter_loop",
|
|
||||||
"-Dclippy::filter_map_next",
|
|
||||||
"-Dclippy::flat_map_option",
|
|
||||||
"-Dclippy::let_unit_value",
|
|
||||||
"-Dclippy::manual_assert",
|
|
||||||
"-Dclippy::manual_ok_or",
|
|
||||||
"-Dclippy::todo",
|
|
||||||
"-Dclippy::unnecessary_wraps",
|
|
||||||
"-Dclippy::useless_transmute",
|
|
||||||
"-Dclippy::used_underscore_binding",
|
|
||||||
"-Delided_lifetimes_in_paths",
|
|
||||||
"-Dunused_lifetimes",
|
|
||||||
"-Drust_2021_prelude_collisions"
|
|
||||||
]
|
|
|
@ -25,7 +25,7 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
continue-on-error: ${{ endsWith(inputs.python-version, '-dev') || contains(fromJSON('["3.7", "pypy3.7"]'), inputs.python-version) }}
|
continue-on-error: ${{ endsWith(inputs.python-version, '-dev') || contains(fromJSON('["3.7", "pypy3.7"]'), inputs.python-version) || inputs.rust == 'beta' || inputs.rust == 'nightly' }}
|
||||||
runs-on: ${{ inputs.os }}
|
runs-on: ${{ inputs.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
@ -65,7 +65,7 @@ jobs:
|
||||||
run: echo "TRYBUILD=overwrite" >> "$GITHUB_ENV"
|
run: echo "TRYBUILD=overwrite" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Build docs
|
- name: Build docs
|
||||||
run: cargo doc --no-deps --no-default-features --features "full ${{ inputs.extra-features }}"
|
run: nox -s docs
|
||||||
|
|
||||||
- name: Build (no features)
|
- name: Build (no features)
|
||||||
run: cargo build --lib --tests --no-default-features
|
run: cargo build --lib --tests --no-default-features
|
||||||
|
|
|
@ -27,10 +27,19 @@ jobs:
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
- name: Check python formatting (black)
|
- name: Check python formatting and lints (ruff)
|
||||||
run: nox -s fmt-py
|
run: nox -s ruff
|
||||||
- name: Check rust formatting (rustfmt)
|
- name: Check rust formatting (rustfmt)
|
||||||
run: nox -s fmt-rust
|
run: nox -s rustfmt
|
||||||
|
|
||||||
|
semver-checks:
|
||||||
|
if: github.ref != 'refs/heads/main'
|
||||||
|
needs: [fmt]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
- uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||||
|
|
||||||
check-msrv:
|
check-msrv:
|
||||||
needs: [fmt]
|
needs: [fmt]
|
||||||
|
@ -104,7 +113,17 @@ jobs:
|
||||||
rust-target: "i686-pc-windows-msvc",
|
rust-target: "i686-pc-windows-msvc",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
include:
|
||||||
|
# Run beta clippy as a way to detect any incoming lints which may affect downstream users
|
||||||
|
- rust: beta
|
||||||
|
platform:
|
||||||
|
{
|
||||||
|
os: "ubuntu-latest",
|
||||||
|
python-architecture: "x64",
|
||||||
|
rust-target: "x86_64-unknown-linux-gnu",
|
||||||
|
}
|
||||||
name: clippy/${{ matrix.platform.rust-target }}/${{ matrix.rust }}
|
name: clippy/${{ matrix.platform.rust-target }}/${{ matrix.rust }}
|
||||||
|
continue-on-error: ${{ matrix.platform.rust != 'stable' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@master
|
- uses: dtolnay/rust-toolchain@master
|
||||||
|
@ -144,7 +163,7 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
extra-features: ["multiple-pymethods"]
|
extra-features: ["multiple-pymethods"]
|
||||||
rust: [stable]
|
rust: [stable]
|
||||||
python-version: ["3.11"]
|
python-version: ["3.12"]
|
||||||
platform:
|
platform:
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
@ -221,7 +240,7 @@ jobs:
|
||||||
include:
|
include:
|
||||||
# Test minimal supported Rust version
|
# Test minimal supported Rust version
|
||||||
- rust: 1.56.0
|
- rust: 1.56.0
|
||||||
python-version: "3.11"
|
python-version: "3.12"
|
||||||
platform:
|
platform:
|
||||||
{
|
{
|
||||||
os: "ubuntu-latest",
|
os: "ubuntu-latest",
|
||||||
|
@ -233,7 +252,7 @@ jobs:
|
||||||
|
|
||||||
# Test the `nightly` feature
|
# Test the `nightly` feature
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
python-version: "3.11"
|
python-version: "3.12"
|
||||||
platform:
|
platform:
|
||||||
{
|
{
|
||||||
os: "ubuntu-latest",
|
os: "ubuntu-latest",
|
||||||
|
@ -244,7 +263,7 @@ jobs:
|
||||||
|
|
||||||
# Test 32-bit Windows only with the latest Python version
|
# Test 32-bit Windows only with the latest Python version
|
||||||
- rust: stable
|
- rust: stable
|
||||||
python-version: "3.11"
|
python-version: "3.12"
|
||||||
platform:
|
platform:
|
||||||
{
|
{
|
||||||
os: "windows-latest",
|
os: "windows-latest",
|
||||||
|
@ -287,13 +306,29 @@ jobs:
|
||||||
- uses: dtolnay/rust-toolchain@nightly
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
with:
|
with:
|
||||||
components: rust-src
|
components: rust-src
|
||||||
- run: cargo install cargo-careful
|
- uses: taiki-e/install-action@cargo-careful
|
||||||
- run: python -m pip install --upgrade pip && pip install nox
|
- run: python -m pip install --upgrade pip && pip install nox
|
||||||
- run: nox -s test-rust -- careful skip-full
|
- run: nox -s test-rust -- careful skip-full
|
||||||
env:
|
env:
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
TRYBUILD: overwrite
|
TRYBUILD: overwrite
|
||||||
|
|
||||||
|
docsrs:
|
||||||
|
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || (github.event_name != 'pull_request' && github.ref != 'refs/heads/main') }}
|
||||||
|
needs: [fmt]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
key: cargo-careful
|
||||||
|
continue-on-error: true
|
||||||
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
|
with:
|
||||||
|
components: rust-src
|
||||||
|
- run: cargo rustdoc --lib --no-default-features --features full -Zunstable-options --config "build.rustdocflags=[\"--cfg\", \"docsrs\"]"
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
needs: [fmt]
|
needs: [fmt]
|
||||||
name: coverage-${{ matrix.os }}
|
name: coverage-${{ matrix.os }}
|
||||||
|
@ -318,7 +353,7 @@ jobs:
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
if: steps.should-skip.outputs.skip != 'true'
|
if: steps.should-skip.outputs.skip != 'true'
|
||||||
with:
|
with:
|
||||||
components: llvm-tools-preview
|
components: llvm-tools-preview,rust-src
|
||||||
- name: Install cargo-llvm-cov
|
- name: Install cargo-llvm-cov
|
||||||
if: steps.should-skip.outputs.skip != 'true'
|
if: steps.should-skip.outputs.skip != 'true'
|
||||||
uses: taiki-e/install-action@cargo-llvm-cov
|
uses: taiki-e/install-action@cargo-llvm-cov
|
||||||
|
@ -334,17 +369,21 @@ jobs:
|
||||||
|
|
||||||
emscripten:
|
emscripten:
|
||||||
name: emscripten
|
name: emscripten
|
||||||
if: ${{ github.event_name != 'pull_request' && github.ref != 'refs/heads/main' }}
|
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || (github.event_name != 'pull_request' && github.ref != 'refs/heads/main') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
# TODO bump emscripten builds to test on 3.12
|
||||||
|
python-version: 3.11
|
||||||
id: setup-python
|
id: setup-python
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: wasm32-unknown-emscripten
|
targets: wasm32-unknown-emscripten
|
||||||
- uses: actions/setup-node@v3
|
components: rust-src
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 14
|
||||||
- run: python -m pip install --upgrade pip && pip install nox
|
- run: python -m pip install --upgrade pip && pip install nox
|
||||||
|
@ -364,6 +403,8 @@ jobs:
|
||||||
run: nox -s test-emscripten
|
run: nox -s test-emscripten
|
||||||
|
|
||||||
test-debug:
|
test-debug:
|
||||||
|
needs: [fmt]
|
||||||
|
if: github.ref != 'refs/heads/main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
@ -406,6 +447,34 @@ jobs:
|
||||||
echo PYO3_CONFIG_FILE=$PYO3_CONFIG_FILE >> $GITHUB_ENV
|
echo PYO3_CONFIG_FILE=$PYO3_CONFIG_FILE >> $GITHUB_ENV
|
||||||
- run: python3 -m nox -s test
|
- run: python3 -m nox -s test
|
||||||
|
|
||||||
|
test-version-limits:
|
||||||
|
needs: [fmt]
|
||||||
|
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || (github.event_name != 'pull_request' && github.ref != 'refs/heads/main') }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
continue-on-error: true
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- run: python3 -m pip install --upgrade pip && pip install nox
|
||||||
|
- run: python3 -m nox -s test-version-limits
|
||||||
|
|
||||||
|
check-feature-powerset:
|
||||||
|
needs: [fmt]
|
||||||
|
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || (github.event_name != 'pull_request' && github.ref != 'refs/heads/main') }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
continue-on-error: true
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: rust-src
|
||||||
|
- uses: taiki-e/install-action@cargo-hack
|
||||||
|
- run: python3 -m pip install --upgrade pip && pip install nox
|
||||||
|
- run: python3 -m nox -s check-feature-powerset
|
||||||
|
|
||||||
conclusion:
|
conclusion:
|
||||||
needs:
|
needs:
|
||||||
- fmt
|
- fmt
|
||||||
|
@ -415,8 +484,12 @@ jobs:
|
||||||
- build-full
|
- build-full
|
||||||
- valgrind
|
- valgrind
|
||||||
- careful
|
- careful
|
||||||
|
- docsrs
|
||||||
- coverage
|
- coverage
|
||||||
- emscripten
|
- emscripten
|
||||||
|
- test-debug
|
||||||
|
- test-version-limits
|
||||||
|
- check-feature-powerset
|
||||||
if: always()
|
if: always()
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -23,3 +23,4 @@ valgrind-python.supp
|
||||||
*.pyd
|
*.pyd
|
||||||
lcov.info
|
lcov.info
|
||||||
netlify_build/
|
netlify_build/
|
||||||
|
.nox/
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
3.11
|
3.12
|
||||||
|
|
46
CHANGELOG.md
46
CHANGELOG.md
|
@ -10,6 +10,47 @@ To see unreleased changes, please see the [CHANGELOG on the main branch guide](h
|
||||||
|
|
||||||
<!-- towncrier release notes start -->
|
<!-- towncrier release notes start -->
|
||||||
|
|
||||||
|
## [0.20.3] - 2024-02-23
|
||||||
|
|
||||||
|
### Packaging
|
||||||
|
|
||||||
|
- Add `portable-atomic` dependency. [#3619](https://github.com/PyO3/pyo3/pull/3619)
|
||||||
|
- Check maximum version of Python at build time and for versions not yet supported require opt-in to the `abi3` stable ABI by the environment variable `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1`. [#3821](https://github.com/PyO3/pyo3/pull/3821)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Use `portable-atomic` to support platforms without 64-bit atomics. [#3619](https://github.com/PyO3/pyo3/pull/3619)
|
||||||
|
- Fix compilation failure with `either` feature enabled without `experimental-inspect` enabled. [#3834](https://github.com/PyO3/pyo3/pull/3834)
|
||||||
|
|
||||||
|
## [0.20.2] - 2024-01-04
|
||||||
|
|
||||||
|
### Packaging
|
||||||
|
|
||||||
|
- Pin `pyo3` and `pyo3-ffi` dependencies on `pyo3-build-config` to require the same patch version, i.e. `pyo3` 0.20.2 requires _exactly_ `pyo3-build-config` 0.20.2. [#3721](https://github.com/PyO3/pyo3/pull/3721)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix compile failure when building `pyo3` 0.20.0 with latest `pyo3-build-config` 0.20.X. [#3724](https://github.com/PyO3/pyo3/pull/3724)
|
||||||
|
- Fix docs.rs build. [#3722](https://github.com/PyO3/pyo3/pull/3722)
|
||||||
|
|
||||||
|
## [0.20.1] - 2023-12-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add optional `either` feature to add conversions for `either::Either<L, R>` sum type. [#3456](https://github.com/PyO3/pyo3/pull/3456)
|
||||||
|
- Add optional `smallvec` feature to add conversions for `smallvec::SmallVec`. [#3507](https://github.com/PyO3/pyo3/pull/3507)
|
||||||
|
- Add `take` and `into_inner` methods to `GILOnceCell` [#3556](https://github.com/PyO3/pyo3/pull/3556)
|
||||||
|
- `#[classmethod]` methods can now also receive `Py<PyType>` as their first argument. [#3587](https://github.com/PyO3/pyo3/pull/3587)
|
||||||
|
- `#[pyfunction(pass_module)]` can now also receive `Py<PyModule>` as their first argument. [#3587](https://github.com/PyO3/pyo3/pull/3587)
|
||||||
|
- Add `traverse` method to `GILProtected`. [#3616](https://github.com/PyO3/pyo3/pull/3616)
|
||||||
|
- Added `abi3-py312` feature [#3687](https://github.com/PyO3/pyo3/pull/3687)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix minimum version specification for optional `chrono` dependency. [#3512](https://github.com/PyO3/pyo3/pull/3512)
|
||||||
|
- Silenced new `clippy::unnecessary_fallible_conversions` warning when using a `Py<Self>` `self` receiver. [#3564](https://github.com/PyO3/pyo3/pull/3564)
|
||||||
|
|
||||||
|
|
||||||
## [0.20.0] - 2023-10-11
|
## [0.20.0] - 2023-10-11
|
||||||
|
|
||||||
### Packaging
|
### Packaging
|
||||||
|
@ -1599,7 +1640,10 @@ Yanked
|
||||||
|
|
||||||
- Initial release
|
- Initial release
|
||||||
|
|
||||||
[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.20.0...HEAD
|
[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.20.3...HEAD
|
||||||
|
[0.20.3]: https://github.com/pyo3/pyo3/compare/v0.20.2...v0.20.3
|
||||||
|
[0.20.2]: https://github.com/pyo3/pyo3/compare/v0.20.1...v0.20.2
|
||||||
|
[0.20.1]: https://github.com/pyo3/pyo3/compare/v0.20.0...v0.20.1
|
||||||
[0.20.0]: https://github.com/pyo3/pyo3/compare/v0.19.2...v0.20.0
|
[0.20.0]: https://github.com/pyo3/pyo3/compare/v0.19.2...v0.20.0
|
||||||
[0.19.2]: https://github.com/pyo3/pyo3/compare/v0.19.1...v0.19.2
|
[0.19.2]: https://github.com/pyo3/pyo3/compare/v0.19.1...v0.19.2
|
||||||
[0.19.1]: https://github.com/pyo3/pyo3/compare/v0.19.0...v0.19.1
|
[0.19.1]: https://github.com/pyo3/pyo3/compare/v0.19.0...v0.19.1
|
||||||
|
|
72
Cargo.toml
72
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pyo3"
|
name = "pyo3"
|
||||||
version = "0.20.0"
|
version = "0.20.3"
|
||||||
description = "Bindings to Python interpreter"
|
description = "Bindings to Python interpreter"
|
||||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -19,12 +19,13 @@ cfg-if = "1.0"
|
||||||
libc = "0.2.62"
|
libc = "0.2.62"
|
||||||
parking_lot = ">= 0.11, < 0.13"
|
parking_lot = ">= 0.11, < 0.13"
|
||||||
memoffset = "0.9"
|
memoffset = "0.9"
|
||||||
|
portable-atomic = "1.0"
|
||||||
|
|
||||||
# ffi bindings to the python interpreter, split into a separate crate so they can be used independently
|
# ffi bindings to the python interpreter, split into a separate crate so they can be used independently
|
||||||
pyo3-ffi = { path = "pyo3-ffi", version = "=0.20.0" }
|
pyo3-ffi = { path = "pyo3-ffi", version = "=0.20.3" }
|
||||||
|
|
||||||
# support crates for macros feature
|
# support crates for macros feature
|
||||||
pyo3-macros = { path = "pyo3-macros", version = "=0.20.0", optional = true }
|
pyo3-macros = { path = "pyo3-macros", version = "=0.20.3", optional = true }
|
||||||
indoc = { version = "2.0.1", optional = true }
|
indoc = { version = "2.0.1", optional = true }
|
||||||
unindent = { version = "0.2.1", optional = true }
|
unindent = { version = "0.2.1", optional = true }
|
||||||
|
|
||||||
|
@ -33,7 +34,8 @@ inventory = { version = "0.3.0", optional = true }
|
||||||
|
|
||||||
# crate integrations that can be added using the eponymous features
|
# crate integrations that can be added using the eponymous features
|
||||||
anyhow = { version = "1.0", optional = true }
|
anyhow = { version = "1.0", optional = true }
|
||||||
chrono = { version = "0.4", default-features = false, optional = true }
|
chrono = { version = "0.4.25", default-features = false, optional = true }
|
||||||
|
either = { version = "1.9", optional = true }
|
||||||
eyre = { version = ">= 0.4, < 0.7", optional = true }
|
eyre = { version = ">= 0.4, < 0.7", optional = true }
|
||||||
hashbrown = { version = ">= 0.9, < 0.15", optional = true }
|
hashbrown = { version = ">= 0.9, < 0.15", optional = true }
|
||||||
indexmap = { version = ">= 1.6, < 3", optional = true }
|
indexmap = { version = ">= 1.6, < 3", optional = true }
|
||||||
|
@ -41,6 +43,7 @@ num-bigint = { version = "0.4", optional = true }
|
||||||
num-complex = { version = ">= 0.2, < 0.5", optional = true }
|
num-complex = { version = ">= 0.2, < 0.5", optional = true }
|
||||||
rust_decimal = { version = "1.0.0", default-features = false, optional = true }
|
rust_decimal = { version = "1.0.0", default-features = false, optional = true }
|
||||||
serde = { version = "1.0", optional = true }
|
serde = { version = "1.0", optional = true }
|
||||||
|
smallvec = { version = "1.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_approx_eq = "1.1.0"
|
assert_approx_eq = "1.1.0"
|
||||||
|
@ -51,12 +54,11 @@ proptest = { version = "1.0", default-features = false, features = ["std"] }
|
||||||
send_wrapper = "0.6"
|
send_wrapper = "0.6"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0.61"
|
serde_json = "1.0.61"
|
||||||
rayon = "1.0.2"
|
rayon = "1.6.1"
|
||||||
rust_decimal = { version = "1.8.0", features = ["std"] }
|
|
||||||
widestring = "0.5.1"
|
widestring = "0.5.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
pyo3-build-config = { path = "pyo3-build-config", version = "0.20.0", features = ["resolve-config"] }
|
pyo3-build-config = { path = "pyo3-build-config", version = "=0.20.3", features = ["resolve-config"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["macros"]
|
default = ["macros"]
|
||||||
|
@ -77,14 +79,15 @@ multiple-pymethods = ["inventory", "pyo3-macros/multiple-pymethods"]
|
||||||
extension-module = ["pyo3-ffi/extension-module"]
|
extension-module = ["pyo3-ffi/extension-module"]
|
||||||
|
|
||||||
# Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more.
|
# Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more.
|
||||||
abi3 = ["pyo3-build-config/abi3", "pyo3-ffi/abi3", "pyo3-macros/abi3"]
|
abi3 = ["pyo3-build-config/abi3", "pyo3-ffi/abi3"]
|
||||||
|
|
||||||
# With abi3, we can manually set the minimum Python version.
|
# With abi3, we can manually set the minimum Python version.
|
||||||
abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37", "pyo3-ffi/abi3-py37"]
|
abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37", "pyo3-ffi/abi3-py37"]
|
||||||
abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38", "pyo3-ffi/abi3-py38"]
|
abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38", "pyo3-ffi/abi3-py38"]
|
||||||
abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39", "pyo3-ffi/abi3-py39"]
|
abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39", "pyo3-ffi/abi3-py39"]
|
||||||
abi3-py310 = ["abi3-py311", "pyo3-build-config/abi3-py310", "pyo3-ffi/abi3-py310"]
|
abi3-py310 = ["abi3-py311", "pyo3-build-config/abi3-py310", "pyo3-ffi/abi3-py310"]
|
||||||
abi3-py311 = ["abi3", "pyo3-build-config/abi3-py311", "pyo3-ffi/abi3-py311"]
|
abi3-py311 = ["abi3-py312", "pyo3-build-config/abi3-py311", "pyo3-ffi/abi3-py311"]
|
||||||
|
abi3-py312 = ["abi3", "pyo3-build-config/abi3-py312", "pyo3-ffi/abi3-py312"]
|
||||||
|
|
||||||
# Automatically generates `python3.dll` import libraries for Windows targets.
|
# Automatically generates `python3.dll` import libraries for Windows targets.
|
||||||
generate-import-lib = ["pyo3-ffi/generate-import-lib"]
|
generate-import-lib = ["pyo3-ffi/generate-import-lib"]
|
||||||
|
@ -95,21 +98,26 @@ auto-initialize = []
|
||||||
# Optimizes PyObject to Vec conversion and so on.
|
# Optimizes PyObject to Vec conversion and so on.
|
||||||
nightly = []
|
nightly = []
|
||||||
|
|
||||||
|
# Disables the checks for use in subinterpreters.
|
||||||
|
unsafe-allow-subinterpreters = []
|
||||||
|
|
||||||
# Activates all additional features
|
# Activates all additional features
|
||||||
# This is mostly intended for testing purposes - activating *all* of these isn't particularly useful.
|
# This is mostly intended for testing purposes - activating *all* of these isn't particularly useful.
|
||||||
full = [
|
full = [
|
||||||
"macros",
|
"macros",
|
||||||
# "multiple-pymethods", # TODO re-add this when MSRV is greater than 1.62
|
# "multiple-pymethods", # TODO re-add this when MSRV is greater than 1.62
|
||||||
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"either",
|
||||||
|
"experimental-inspect",
|
||||||
|
"eyre",
|
||||||
|
"hashbrown",
|
||||||
|
"indexmap",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-complex",
|
"num-complex",
|
||||||
"hashbrown",
|
|
||||||
"serde",
|
|
||||||
"indexmap",
|
|
||||||
"eyre",
|
|
||||||
"anyhow",
|
|
||||||
"experimental-inspect",
|
|
||||||
"rust_decimal",
|
"rust_decimal",
|
||||||
|
"serde",
|
||||||
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
@ -124,5 +132,37 @@ members = [
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
no-default-features = true
|
no-default-features = true
|
||||||
features = ["macros", "num-bigint", "num-complex", "hashbrown", "serde", "multiple-pymethods", "indexmap", "eyre", "chrono", "rust_decimal"]
|
features = ["full"]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
checked_conversions = "warn"
|
||||||
|
dbg_macro = "warn"
|
||||||
|
explicit_into_iter_loop = "warn"
|
||||||
|
explicit_iter_loop = "warn"
|
||||||
|
filter_map_next = "warn"
|
||||||
|
flat_map_option = "warn"
|
||||||
|
let_unit_value = "warn"
|
||||||
|
manual_assert = "warn"
|
||||||
|
manual_ok_or = "warn"
|
||||||
|
todo = "warn"
|
||||||
|
unnecessary_wraps = "warn"
|
||||||
|
useless_transmute = "warn"
|
||||||
|
used_underscore_binding = "warn"
|
||||||
|
|
||||||
|
[workspace.lints.rust]
|
||||||
|
elided_lifetimes_in_paths = "warn"
|
||||||
|
invalid_doc_attributes = "warn"
|
||||||
|
rust_2018_idioms = "warn"
|
||||||
|
rust_2021_prelude_collisions = "warn"
|
||||||
|
unused_lifetimes = "warn"
|
||||||
|
# rust nightly got stricter about these since PyO3 0.20 was released
|
||||||
|
dead_code = "allow"
|
||||||
|
unused_imports = "allow"
|
||||||
|
|
||||||
|
[workspace.lints.rustdoc]
|
||||||
|
broken_intra_doc_links = "warn"
|
||||||
|
bare_urls = "warn"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
|
@ -68,7 +68,7 @@ name = "string_sum"
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pyo3 = { version = "0.20.0", features = ["extension-module"] }
|
pyo3 = { version = "0.20.3", features = ["extension-module"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
**`src/lib.rs`**
|
**`src/lib.rs`**
|
||||||
|
@ -137,7 +137,7 @@ Start a new project with `cargo new` and add `pyo3` to the `Cargo.toml` like th
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies.pyo3]
|
[dependencies.pyo3]
|
||||||
version = "0.20.0"
|
version = "0.20.3"
|
||||||
features = ["auto-initialize"]
|
features = ["auto-initialize"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
6
build.rs
6
build.rs
|
@ -33,10 +33,12 @@ fn ensure_auto_initialize_ok(interpreter_config: &InterpreterConfig) -> Result<(
|
||||||
fn configure_pyo3() -> Result<()> {
|
fn configure_pyo3() -> Result<()> {
|
||||||
let interpreter_config = pyo3_build_config::get();
|
let interpreter_config = pyo3_build_config::get();
|
||||||
|
|
||||||
interpreter_config.emit_pyo3_cfgs();
|
|
||||||
|
|
||||||
ensure_auto_initialize_ok(interpreter_config)?;
|
ensure_auto_initialize_ok(interpreter_config)?;
|
||||||
|
|
||||||
|
for cfg in interpreter_config.build_script_outputs() {
|
||||||
|
println!("{}", cfg)
|
||||||
|
}
|
||||||
|
|
||||||
// Emit cfgs like `thread_local_const_init`
|
// Emit cfgs like `thread_local_const_init`
|
||||||
print_feature_cfgs();
|
print_feature_cfgs();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
pybuilddir.txt
|
|
@ -5,7 +5,7 @@ publish = false
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pyo3 = { version = "0.20.0", path = "..", features = ["auto-initialize", "extension-module"] }
|
pyo3 = { version = "0.20.3", path = "..", features = ["auto-initialize", "extension-module"] }
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "decorator"
|
name = "decorator"
|
||||||
|
|
|
@ -11,6 +11,7 @@ Below is a brief description of each of these:
|
||||||
| `setuptools-rust-starter` | A template project which is configured to use [`setuptools_rust`](https://github.com/PyO3/setuptools-rust/) for development. |
|
| `setuptools-rust-starter` | A template project which is configured to use [`setuptools_rust`](https://github.com/PyO3/setuptools-rust/) for development. |
|
||||||
| `word-count` | A quick performance comparison between word counter implementations written in each of Rust and Python. |
|
| `word-count` | A quick performance comparison between word counter implementations written in each of Rust and Python. |
|
||||||
| `plugin` | Illustrates how to use Python as a scripting language within a Rust application |
|
| `plugin` | Illustrates how to use Python as a scripting language within a Rust application |
|
||||||
|
| `sequential` | Illustrates how to use pyo3-ffi to write subinterpreter-safe modules |
|
||||||
|
|
||||||
## Creating new projects from these examples
|
## Creating new projects from these examples
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
variable::set("PYO3_VERSION", "0.20.0");
|
variable::set("PYO3_VERSION", "0.20.3");
|
||||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||||
file::rename(".template/pyproject.toml", "pyproject.toml");
|
file::rename(".template/pyproject.toml", "pyproject.toml");
|
||||||
file::delete(".template");
|
file::delete(".template");
|
||||||
|
|
|
@ -5,3 +5,6 @@ build-backend = "maturin"
|
||||||
[project]
|
[project]
|
||||||
name = "{{project-name}}"
|
name = "{{project-name}}"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest"]
|
||||||
|
|
|
@ -2,8 +2,7 @@ import nox
|
||||||
|
|
||||||
|
|
||||||
@nox.session
|
@nox.session
|
||||||
def python(session):
|
def python(session: nox.Session):
|
||||||
session.install("-rrequirements-dev.txt")
|
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||||
session.install("maturin")
|
session.install(".[dev]")
|
||||||
session.run_always("maturin", "develop")
|
|
||||||
session.run("pytest")
|
session.run("pytest")
|
||||||
|
|
|
@ -14,3 +14,6 @@ classifiers = [
|
||||||
"Operating System :: POSIX",
|
"Operating System :: POSIX",
|
||||||
"Operating System :: MacOS :: MacOS X",
|
"Operating System :: MacOS :: MacOS X",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest"]
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
pytest>=3.5.0
|
|
||||||
pip>=21.3
|
|
||||||
maturin>=0.12,<0.13
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
from decorator import Counter
|
||||||
|
|
||||||
|
|
||||||
@Counter
|
@Counter
|
||||||
def say_hello():
|
def say_hello():
|
||||||
print("hello")
|
print("hello")
|
||||||
|
|
|
@ -45,7 +45,7 @@ def test_discussion_2598():
|
||||||
@Counter
|
@Counter
|
||||||
def say_hello():
|
def say_hello():
|
||||||
if say_hello.count < 2:
|
if say_hello.count < 2:
|
||||||
print(f"hello from decorator")
|
print("hello from decorator")
|
||||||
|
|
||||||
say_hello()
|
say_hello()
|
||||||
say_hello()
|
say_hello()
|
||||||
|
|
|
@ -5,3 +5,6 @@ build-backend = "maturin"
|
||||||
[project]
|
[project]
|
||||||
name = "{{project-name}}"
|
name = "{{project-name}}"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest"]
|
||||||
|
|
|
@ -2,8 +2,7 @@ import nox
|
||||||
|
|
||||||
|
|
||||||
@nox.session
|
@nox.session
|
||||||
def python(session):
|
def python(session: nox.Session):
|
||||||
session.install("-rrequirements-dev.txt")
|
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||||
session.install("maturin")
|
session.install(".[dev]")
|
||||||
session.run_always("maturin", "develop")
|
|
||||||
session.run("pytest")
|
session.run("pytest")
|
||||||
|
|
|
@ -14,3 +14,6 @@ classifiers = [
|
||||||
"Operating System :: POSIX",
|
"Operating System :: POSIX",
|
||||||
"Operating System :: MacOS :: MacOS X",
|
"Operating System :: MacOS :: MacOS X",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest"]
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
pytest>=3.5.0
|
|
||||||
pip>=21.3
|
|
||||||
maturin>=1,<2
|
|
|
@ -1,4 +1,4 @@
|
||||||
variable::set("PYO3_VERSION", "0.20.0");
|
variable::set("PYO3_VERSION", "0.20.3");
|
||||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||||
file::rename(".template/pyproject.toml", "pyproject.toml");
|
file::rename(".template/pyproject.toml", "pyproject.toml");
|
||||||
file::delete(".template");
|
file::delete(".template");
|
||||||
|
|
|
@ -5,3 +5,6 @@ build-backend = "maturin"
|
||||||
[project]
|
[project]
|
||||||
name = "{{project-name}}"
|
name = "{{project-name}}"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest"]
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
# import the contents of the Rust library into the Python extension
|
# import the contents of the Rust library into the Python extension
|
||||||
# optional: include the documentation from the Rust module
|
|
||||||
from .maturin_starter import *
|
from .maturin_starter import *
|
||||||
from .maturin_starter import __all__, __doc__
|
from .maturin_starter import __all__
|
||||||
|
|
||||||
|
# optional: include the documentation from the Rust module
|
||||||
|
from .maturin_starter import __doc__ # noqa: F401
|
||||||
|
|
||||||
__all__ = __all__ + ["PythonClass"]
|
__all__ = __all__ + ["PythonClass"]
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import nox
|
||||||
|
|
||||||
@nox.session
|
@nox.session
|
||||||
def python(session):
|
def python(session):
|
||||||
session.install("-rrequirements-dev.txt")
|
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||||
session.install("maturin")
|
session.install(".[dev]")
|
||||||
session.run_always("maturin", "develop")
|
|
||||||
session.run("pytest")
|
session.run("pytest")
|
||||||
|
|
|
@ -14,3 +14,6 @@ classifiers = [
|
||||||
"Operating System :: POSIX",
|
"Operating System :: POSIX",
|
||||||
"Operating System :: MacOS :: MacOS X",
|
"Operating System :: MacOS :: MacOS X",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest"]
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
pytest>=3.5.0
|
|
||||||
pip>=21.3
|
|
||||||
maturin>=0.12,<0.13
|
|
|
@ -1,4 +1,4 @@
|
||||||
variable::set("PYO3_VERSION", "0.20.0");
|
variable::set("PYO3_VERSION", "0.20.3");
|
||||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||||
file::rename(".template/plugin_api/Cargo.toml", "plugin_api/Cargo.toml");
|
file::rename(".template/plugin_api/Cargo.toml", "plugin_api/Cargo.toml");
|
||||||
file::delete(".template");
|
file::delete(".template");
|
||||||
|
|
|
@ -3,7 +3,6 @@ import nox
|
||||||
|
|
||||||
@nox.session
|
@nox.session
|
||||||
def python(session):
|
def python(session):
|
||||||
session.install("-rrequirements-dev.txt")
|
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||||
session.install("maturin")
|
session.install(".[dev]")
|
||||||
session.run_always("maturin", "develop", "--features", "extension-module")
|
|
||||||
session.run("pytest")
|
session.run("pytest")
|
||||||
|
|
|
@ -10,3 +10,6 @@ classifiers = [
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest"]
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
pytest>=3.5.0
|
|
||||||
pip>=21.3
|
|
||||||
maturin>=0.14
|
|
|
@ -1,2 +1,2 @@
|
||||||
def test_import():
|
def test_import():
|
||||||
import plugin_api
|
import plugin_api # noqa: F401
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
authors = ["{{authors}}"]
|
||||||
|
name = "{{project-name}}"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "sequential"
|
||||||
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pyo3-ffi = { version = "{{PYO3_VERSION}}", features = ["extension-module"] }
|
|
@ -0,0 +1,4 @@
|
||||||
|
variable::set("PYO3_VERSION", "0.19.2");
|
||||||
|
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||||
|
file::rename(".template/pyproject.toml", "pyproject.toml");
|
||||||
|
file::delete(".template");
|
|
@ -0,0 +1,7 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["maturin>=1,<2"]
|
||||||
|
build-backend = "maturin"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "{{project-name}}"
|
||||||
|
version = "0.1.0"
|
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "sequential"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "sequential"
|
||||||
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pyo3-ffi = { path = "../../pyo3-ffi", features = ["extension-module"] }
|
||||||
|
|
||||||
|
[workspace]
|
|
@ -0,0 +1,2 @@
|
||||||
|
include pyproject.toml Cargo.toml
|
||||||
|
recursive-include src *
|
|
@ -0,0 +1,36 @@
|
||||||
|
# sequential
|
||||||
|
|
||||||
|
A project built using only `pyo3_ffi`, without any of PyO3's safe api. It can be executed by subinterpreters that have their own GIL.
|
||||||
|
|
||||||
|
## Building and Testing
|
||||||
|
|
||||||
|
To build this package, first install `maturin`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pip install maturin
|
||||||
|
```
|
||||||
|
|
||||||
|
To build and test use `maturin develop`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pip install -r requirements-dev.txt
|
||||||
|
maturin develop
|
||||||
|
pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, install nox and run the tests inside an isolated environment:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
nox
|
||||||
|
```
|
||||||
|
|
||||||
|
## Copying this example
|
||||||
|
|
||||||
|
Use [`cargo-generate`](https://crates.io/crates/cargo-generate):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cargo install cargo-generate
|
||||||
|
$ cargo generate --git https://github.com/PyO3/pyo3 examples/sequential
|
||||||
|
```
|
||||||
|
|
||||||
|
(`cargo generate` will take a little while to clone the PyO3 repo first; be patient when waiting for the command to run.)
|
|
@ -0,0 +1,5 @@
|
||||||
|
[template]
|
||||||
|
ignore = [".nox"]
|
||||||
|
|
||||||
|
[hooks]
|
||||||
|
pre = [".template/pre-script.rhai"]
|
|
@ -0,0 +1,11 @@
|
||||||
|
import sys
|
||||||
|
import nox
|
||||||
|
|
||||||
|
|
||||||
|
@nox.session
|
||||||
|
def python(session):
|
||||||
|
if sys.version_info < (3, 12):
|
||||||
|
session.skip("Python 3.12+ is required")
|
||||||
|
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||||
|
session.install(".[dev]")
|
||||||
|
session.run("pytest")
|
|
@ -0,0 +1,20 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["maturin>=1,<2"]
|
||||||
|
build-backend = "maturin"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "sequential"
|
||||||
|
version = "0.1.0"
|
||||||
|
classifiers = [
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Development Status :: 3 - Alpha",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Rust",
|
||||||
|
"Operating System :: POSIX",
|
||||||
|
"Operating System :: MacOS :: MacOS X",
|
||||||
|
]
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest"]
|
|
@ -0,0 +1,131 @@
|
||||||
|
use core::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
use core::{mem, ptr};
|
||||||
|
use std::os::raw::{c_char, c_int, c_uint, c_ulonglong, c_void};
|
||||||
|
|
||||||
|
use pyo3_ffi::*;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct PyId {
|
||||||
|
_ob_base: PyObject,
|
||||||
|
id: Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
static COUNT: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub struct Id(u64);
|
||||||
|
|
||||||
|
impl Id {
|
||||||
|
fn new() -> Self {
|
||||||
|
Id(COUNT.fetch_add(1, Ordering::Relaxed))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn id_new(
|
||||||
|
subtype: *mut PyTypeObject,
|
||||||
|
args: *mut PyObject,
|
||||||
|
kwds: *mut PyObject,
|
||||||
|
) -> *mut PyObject {
|
||||||
|
if PyTuple_Size(args) != 0 || !kwds.is_null() {
|
||||||
|
PyErr_SetString(
|
||||||
|
PyExc_TypeError,
|
||||||
|
"Id() takes no arguments\0".as_ptr().cast::<c_char>(),
|
||||||
|
);
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
|
||||||
|
let f: allocfunc = (*subtype).tp_alloc.unwrap_or(PyType_GenericAlloc);
|
||||||
|
let slf = f(subtype, 0);
|
||||||
|
|
||||||
|
if slf.is_null() {
|
||||||
|
return ptr::null_mut();
|
||||||
|
} else {
|
||||||
|
let id = Id::new();
|
||||||
|
let slf = slf.cast::<PyId>();
|
||||||
|
ptr::addr_of_mut!((*slf).id).write(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn id_repr(slf: *mut PyObject) -> *mut PyObject {
|
||||||
|
let slf = slf.cast::<PyId>();
|
||||||
|
let id = (*slf).id.0;
|
||||||
|
let string = format!("Id({})", id);
|
||||||
|
PyUnicode_FromStringAndSize(string.as_ptr().cast::<c_char>(), string.len() as Py_ssize_t)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn id_int(slf: *mut PyObject) -> *mut PyObject {
|
||||||
|
let slf = slf.cast::<PyId>();
|
||||||
|
let id = (*slf).id.0;
|
||||||
|
PyLong_FromUnsignedLongLong(id as c_ulonglong)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn id_richcompare(
|
||||||
|
slf: *mut PyObject,
|
||||||
|
other: *mut PyObject,
|
||||||
|
op: c_int,
|
||||||
|
) -> *mut PyObject {
|
||||||
|
let pytype = Py_TYPE(slf); // guaranteed to be `sequential.Id`
|
||||||
|
if Py_TYPE(other) != pytype {
|
||||||
|
return Py_NewRef(Py_NotImplemented());
|
||||||
|
}
|
||||||
|
let slf = (*slf.cast::<PyId>()).id;
|
||||||
|
let other = (*other.cast::<PyId>()).id;
|
||||||
|
|
||||||
|
let cmp = match op {
|
||||||
|
pyo3_ffi::Py_LT => slf < other,
|
||||||
|
pyo3_ffi::Py_LE => slf <= other,
|
||||||
|
pyo3_ffi::Py_EQ => slf == other,
|
||||||
|
pyo3_ffi::Py_NE => slf != other,
|
||||||
|
pyo3_ffi::Py_GT => slf > other,
|
||||||
|
pyo3_ffi::Py_GE => slf >= other,
|
||||||
|
unrecognized => {
|
||||||
|
let msg = format!("unrecognized richcompare opcode {}\0", unrecognized);
|
||||||
|
PyErr_SetString(PyExc_SystemError, msg.as_ptr().cast::<c_char>());
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if cmp {
|
||||||
|
Py_NewRef(Py_True())
|
||||||
|
} else {
|
||||||
|
Py_NewRef(Py_False())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut SLOTS: &[PyType_Slot] = &[
|
||||||
|
PyType_Slot {
|
||||||
|
slot: Py_tp_new,
|
||||||
|
pfunc: id_new as *mut c_void,
|
||||||
|
},
|
||||||
|
PyType_Slot {
|
||||||
|
slot: Py_tp_doc,
|
||||||
|
pfunc: "An id that is increased every time an instance is created\0".as_ptr()
|
||||||
|
as *mut c_void,
|
||||||
|
},
|
||||||
|
PyType_Slot {
|
||||||
|
slot: Py_tp_repr,
|
||||||
|
pfunc: id_repr as *mut c_void,
|
||||||
|
},
|
||||||
|
PyType_Slot {
|
||||||
|
slot: Py_nb_int,
|
||||||
|
pfunc: id_int as *mut c_void,
|
||||||
|
},
|
||||||
|
PyType_Slot {
|
||||||
|
slot: Py_tp_richcompare,
|
||||||
|
pfunc: id_richcompare as *mut c_void,
|
||||||
|
},
|
||||||
|
PyType_Slot {
|
||||||
|
slot: 0,
|
||||||
|
pfunc: ptr::null_mut(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static mut ID_SPEC: PyType_Spec = PyType_Spec {
|
||||||
|
name: "sequential.Id\0".as_ptr().cast::<c_char>(),
|
||||||
|
basicsize: mem::size_of::<PyId>() as c_int,
|
||||||
|
itemsize: 0,
|
||||||
|
flags: (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE) as c_uint,
|
||||||
|
slots: unsafe { SLOTS as *const [PyType_Slot] as *mut PyType_Slot },
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
use pyo3_ffi::*;
|
||||||
|
|
||||||
|
mod id;
|
||||||
|
mod module;
|
||||||
|
use crate::module::MODULE_DEF;
|
||||||
|
|
||||||
|
// The module initialization function, which must be named `PyInit_<your_module>`.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn PyInit_sequential() -> *mut PyObject {
|
||||||
|
PyModuleDef_Init(ptr::addr_of_mut!(MODULE_DEF))
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
use core::{mem, ptr};
|
||||||
|
use pyo3_ffi::*;
|
||||||
|
use std::os::raw::{c_char, c_int, c_void};
|
||||||
|
|
||||||
|
pub static mut MODULE_DEF: PyModuleDef = PyModuleDef {
|
||||||
|
m_base: PyModuleDef_HEAD_INIT,
|
||||||
|
m_name: "sequential\0".as_ptr().cast::<c_char>(),
|
||||||
|
m_doc: "A library for generating sequential ids, written in Rust.\0"
|
||||||
|
.as_ptr()
|
||||||
|
.cast::<c_char>(),
|
||||||
|
m_size: mem::size_of::<sequential_state>() as Py_ssize_t,
|
||||||
|
m_methods: std::ptr::null_mut(),
|
||||||
|
m_slots: unsafe { SEQUENTIAL_SLOTS as *const [PyModuleDef_Slot] as *mut PyModuleDef_Slot },
|
||||||
|
m_traverse: Some(sequential_traverse),
|
||||||
|
m_clear: Some(sequential_clear),
|
||||||
|
m_free: Some(sequential_free),
|
||||||
|
};
|
||||||
|
|
||||||
|
static mut SEQUENTIAL_SLOTS: &[PyModuleDef_Slot] = &[
|
||||||
|
PyModuleDef_Slot {
|
||||||
|
slot: Py_mod_exec,
|
||||||
|
value: sequential_exec as *mut c_void,
|
||||||
|
},
|
||||||
|
PyModuleDef_Slot {
|
||||||
|
slot: Py_mod_multiple_interpreters,
|
||||||
|
value: Py_MOD_PER_INTERPRETER_GIL_SUPPORTED,
|
||||||
|
},
|
||||||
|
PyModuleDef_Slot {
|
||||||
|
slot: 0,
|
||||||
|
value: ptr::null_mut(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
unsafe extern "C" fn sequential_exec(module: *mut PyObject) -> c_int {
|
||||||
|
let state: *mut sequential_state = PyModule_GetState(module).cast();
|
||||||
|
|
||||||
|
let id_type = PyType_FromModuleAndSpec(
|
||||||
|
module,
|
||||||
|
ptr::addr_of_mut!(crate::id::ID_SPEC),
|
||||||
|
ptr::null_mut(),
|
||||||
|
);
|
||||||
|
if id_type.is_null() {
|
||||||
|
PyErr_SetString(
|
||||||
|
PyExc_SystemError,
|
||||||
|
"cannot locate type object\0".as_ptr().cast::<c_char>(),
|
||||||
|
);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
(*state).id_type = id_type.cast::<PyTypeObject>();
|
||||||
|
|
||||||
|
PyModule_AddObjectRef(module, "Id\0".as_ptr().cast::<c_char>(), id_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn sequential_traverse(
|
||||||
|
module: *mut PyObject,
|
||||||
|
visit: visitproc,
|
||||||
|
arg: *mut c_void,
|
||||||
|
) -> c_int {
|
||||||
|
let state: *mut sequential_state = PyModule_GetState(module.cast()).cast();
|
||||||
|
let id_type: *mut PyObject = (*state).id_type.cast();
|
||||||
|
|
||||||
|
if id_type.is_null() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
(visit)(id_type, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn sequential_clear(module: *mut PyObject) -> c_int {
|
||||||
|
let state: *mut sequential_state = PyModule_GetState(module.cast()).cast();
|
||||||
|
Py_CLEAR(ptr::addr_of_mut!((*state).id_type).cast());
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn sequential_free(module: *mut c_void) {
|
||||||
|
sequential_clear(module.cast());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct sequential_state {
|
||||||
|
id_type: *mut PyTypeObject,
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
use core::ffi::{c_char, CStr};
|
||||||
|
use core::ptr;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
use pyo3_ffi::*;
|
||||||
|
use sequential::PyInit_sequential;
|
||||||
|
|
||||||
|
static COMMAND: &'static str = "
|
||||||
|
from sequential import Id
|
||||||
|
|
||||||
|
s = sum(int(Id()) for _ in range(12))
|
||||||
|
\0";
|
||||||
|
|
||||||
|
// Newtype to be able to pass it to another thread.
|
||||||
|
struct State(*mut PyThreadState);
|
||||||
|
unsafe impl Sync for State {}
|
||||||
|
unsafe impl Send for State {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lets_go_fast() -> Result<(), String> {
|
||||||
|
unsafe {
|
||||||
|
let ret = PyImport_AppendInittab(
|
||||||
|
"sequential\0".as_ptr().cast::<c_char>(),
|
||||||
|
Some(PyInit_sequential),
|
||||||
|
);
|
||||||
|
if ret == -1 {
|
||||||
|
return Err("could not add module to inittab".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_Initialize();
|
||||||
|
|
||||||
|
let main_state = PyThreadState_Swap(ptr::null_mut());
|
||||||
|
|
||||||
|
const NULL: State = State(ptr::null_mut());
|
||||||
|
let mut subs = [NULL; 12];
|
||||||
|
|
||||||
|
let config = PyInterpreterConfig {
|
||||||
|
use_main_obmalloc: 0,
|
||||||
|
allow_fork: 0,
|
||||||
|
allow_exec: 0,
|
||||||
|
allow_threads: 1,
|
||||||
|
allow_daemon_threads: 0,
|
||||||
|
check_multi_interp_extensions: 1,
|
||||||
|
gil: PyInterpreterConfig_OWN_GIL,
|
||||||
|
};
|
||||||
|
|
||||||
|
for State(state) in &mut subs {
|
||||||
|
let status = Py_NewInterpreterFromConfig(state, &config);
|
||||||
|
if PyStatus_IsError(status) == 1 {
|
||||||
|
let msg = if status.err_msg.is_null() {
|
||||||
|
"no error message".into()
|
||||||
|
} else {
|
||||||
|
CStr::from_ptr(status.err_msg).to_string_lossy()
|
||||||
|
};
|
||||||
|
PyThreadState_Swap(main_state);
|
||||||
|
Py_FinalizeEx();
|
||||||
|
return Err(format!("could not create new subinterpreter: {msg}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyThreadState_Swap(main_state);
|
||||||
|
|
||||||
|
let main_state = PyEval_SaveThread(); // a PyInterpreterConfig with shared gil would deadlock otherwise
|
||||||
|
|
||||||
|
let ints: Vec<_> = thread::scope(move |s| {
|
||||||
|
let mut handles = vec![];
|
||||||
|
|
||||||
|
for state in subs {
|
||||||
|
let handle = s.spawn(move || {
|
||||||
|
let state = state;
|
||||||
|
PyEval_RestoreThread(state.0);
|
||||||
|
|
||||||
|
let ret = run_code();
|
||||||
|
|
||||||
|
Py_EndInterpreter(state.0);
|
||||||
|
ret
|
||||||
|
});
|
||||||
|
|
||||||
|
handles.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
handles.into_iter().map(|h| h.join().unwrap()).collect()
|
||||||
|
});
|
||||||
|
|
||||||
|
PyEval_RestoreThread(main_state);
|
||||||
|
|
||||||
|
let ret = Py_FinalizeEx();
|
||||||
|
if ret == -1 {
|
||||||
|
return Err("could not finalize interpreter".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sum: u64 = 0;
|
||||||
|
for i in ints {
|
||||||
|
let i = i?;
|
||||||
|
sum += i;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(sum, (0..).take(12 * 12).sum());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn fetch() -> String {
|
||||||
|
let err = PyErr_GetRaisedException();
|
||||||
|
let err_repr = PyObject_Str(err);
|
||||||
|
if !err_repr.is_null() {
|
||||||
|
let mut size = 0;
|
||||||
|
let p = PyUnicode_AsUTF8AndSize(err_repr, &mut size);
|
||||||
|
if !p.is_null() {
|
||||||
|
let s = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
|
||||||
|
p.cast::<u8>(),
|
||||||
|
size as usize,
|
||||||
|
));
|
||||||
|
let s = String::from(s);
|
||||||
|
Py_DECREF(err_repr);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String::from("could not get error")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_code() -> Result<u64, String> {
|
||||||
|
unsafe {
|
||||||
|
let code_obj = Py_CompileString(
|
||||||
|
COMMAND.as_ptr().cast::<c_char>(),
|
||||||
|
"program\0".as_ptr().cast::<c_char>(),
|
||||||
|
Py_file_input,
|
||||||
|
);
|
||||||
|
if code_obj.is_null() {
|
||||||
|
return Err(fetch());
|
||||||
|
}
|
||||||
|
let globals = PyDict_New();
|
||||||
|
let res_ptr = PyEval_EvalCode(code_obj, globals, ptr::null_mut());
|
||||||
|
Py_DECREF(code_obj);
|
||||||
|
if res_ptr.is_null() {
|
||||||
|
return Err(fetch());
|
||||||
|
} else {
|
||||||
|
Py_DECREF(res_ptr);
|
||||||
|
}
|
||||||
|
let sum = PyDict_GetItemString(globals, "s\0".as_ptr().cast::<c_char>()); /* borrowed reference */
|
||||||
|
if sum.is_null() {
|
||||||
|
Py_DECREF(globals);
|
||||||
|
return Err("globals did not have `s`".into());
|
||||||
|
}
|
||||||
|
let int = PyLong_AsUnsignedLongLong(sum) as u64;
|
||||||
|
|
||||||
|
Py_DECREF(globals);
|
||||||
|
Ok(int)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import pytest
|
||||||
|
from sequential import Id
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_some():
|
||||||
|
for x in range(12):
|
||||||
|
i = Id()
|
||||||
|
assert x == int(i)
|
||||||
|
|
||||||
|
|
||||||
|
def test_args():
|
||||||
|
with pytest.raises(TypeError, match="Id\\(\\) takes no arguments"):
|
||||||
|
Id(3, 4)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cmp():
|
||||||
|
a = Id()
|
||||||
|
b = Id()
|
||||||
|
assert a <= b
|
||||||
|
assert a < b
|
||||||
|
assert a == a
|
|
@ -1,4 +1,4 @@
|
||||||
variable::set("PYO3_VERSION", "0.20.0");
|
variable::set("PYO3_VERSION", "0.20.3");
|
||||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||||
file::rename(".template/setup.cfg", "setup.cfg");
|
file::rename(".template/setup.cfg", "setup.cfg");
|
||||||
file::delete(".template");
|
file::delete(".template");
|
||||||
|
|
|
@ -2,7 +2,7 @@ import nox
|
||||||
|
|
||||||
|
|
||||||
@nox.session
|
@nox.session
|
||||||
def python(session):
|
def python(session: nox.Session):
|
||||||
session.install("-rrequirements-dev.txt")
|
session.install("-rrequirements-dev.txt")
|
||||||
session.run_always(
|
session.run_always(
|
||||||
"pip", "install", "-e", ".", "--no-build-isolation", env={"BUILD_DEBUG": "1"}
|
"pip", "install", "-e", ".", "--no-build-isolation", env={"BUILD_DEBUG": "1"}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
# import the contents of the Rust library into the Python extension
|
# import the contents of the Rust library into the Python extension
|
||||||
# optional: include the documentation from the Rust module
|
|
||||||
from ._setuptools_rust_starter import *
|
from ._setuptools_rust_starter import *
|
||||||
from ._setuptools_rust_starter import __all__, __doc__
|
from ._setuptools_rust_starter import __all__
|
||||||
|
|
||||||
|
# optional: include the documentation from the Rust module
|
||||||
|
from ._setuptools_rust_starter import __doc__ # noqa: F401
|
||||||
|
|
||||||
__all__ = __all__ + ["PythonClass"]
|
__all__ = __all__ + ["PythonClass"]
|
||||||
|
|
||||||
|
|
|
@ -5,3 +5,6 @@ build-backend = "maturin"
|
||||||
[project]
|
[project]
|
||||||
name = "{{project-name}}"
|
name = "{{project-name}}"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest"]
|
||||||
|
|
|
@ -2,8 +2,7 @@ import nox
|
||||||
|
|
||||||
|
|
||||||
@nox.session
|
@nox.session
|
||||||
def python(session):
|
def python(session: nox.Session):
|
||||||
session.install("-rrequirements-dev.txt")
|
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||||
session.install("maturin")
|
session.install(".[dev]")
|
||||||
session.run_always("maturin", "develop")
|
|
||||||
session.run("pytest")
|
session.run("pytest")
|
||||||
|
|
|
@ -3,7 +3,7 @@ requires = ["maturin>=1,<2"]
|
||||||
build-backend = "maturin"
|
build-backend = "maturin"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "string sum"
|
name = "string_sum"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
|
@ -14,3 +14,6 @@ classifiers = [
|
||||||
"Operating System :: POSIX",
|
"Operating System :: POSIX",
|
||||||
"Operating System :: MacOS :: MacOS X",
|
"Operating System :: MacOS :: MacOS X",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest"]
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
pytest>=3.5.0
|
|
||||||
pip>=21.3
|
|
||||||
maturin>=0.12,<0.13
|
|
|
@ -14,7 +14,7 @@ def test_err1():
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
TypeError, match="sum_as_string expected an int for positional argument 1"
|
TypeError, match="sum_as_string expected an int for positional argument 1"
|
||||||
) as e:
|
):
|
||||||
sum_as_string(a, b)
|
sum_as_string(a, b)
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,19 +23,19 @@ def test_err2():
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
TypeError, match="sum_as_string expected an int for positional argument 2"
|
TypeError, match="sum_as_string expected an int for positional argument 2"
|
||||||
) as e:
|
):
|
||||||
sum_as_string(a, b)
|
sum_as_string(a, b)
|
||||||
|
|
||||||
|
|
||||||
def test_overflow1():
|
def test_overflow1():
|
||||||
a, b = 0, 1 << 43
|
a, b = 0, 1 << 43
|
||||||
|
|
||||||
with pytest.raises(OverflowError, match="cannot fit 8796093022208 in 32 bits") as e:
|
with pytest.raises(OverflowError, match="cannot fit 8796093022208 in 32 bits"):
|
||||||
sum_as_string(a, b)
|
sum_as_string(a, b)
|
||||||
|
|
||||||
|
|
||||||
def test_overflow2():
|
def test_overflow2():
|
||||||
a, b = 1 << 30, 1 << 30
|
a, b = 1 << 30, 1 << 30
|
||||||
|
|
||||||
with pytest.raises(OverflowError, match="arguments too large to add") as e:
|
with pytest.raises(OverflowError, match="arguments too large to add"):
|
||||||
sum_as_string(a, b)
|
sum_as_string(a, b)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
variable::set("PYO3_VERSION", "0.20.0");
|
variable::set("PYO3_VERSION", "0.20.3");
|
||||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||||
|
file::rename(".template/pyproject.toml", "pyproject.toml");
|
||||||
file::delete(".template");
|
file::delete(".template");
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=41.0.0", "wheel", "setuptools_rust>=1.0.0"]
|
requires = ["maturin>=1,<2"]
|
||||||
|
build-backend = "maturin"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "{{project-name}}"
|
name = "{{project-name}}"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = "--benchmark-disable"
|
addopts = "--benchmark-disable"
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
[metadata]
|
|
||||||
name = {{project-name}}
|
|
||||||
version = 0.1.0
|
|
||||||
packages =
|
|
||||||
word_count
|
|
||||||
|
|
||||||
[options]
|
|
||||||
include_package_data = True
|
|
||||||
zip_safe = False
|
|
|
@ -4,15 +4,14 @@ nox.options.sessions = ["test"]
|
||||||
|
|
||||||
|
|
||||||
@nox.session
|
@nox.session
|
||||||
def test(session):
|
def test(session: nox.Session):
|
||||||
session.install("-rrequirements-dev.txt")
|
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||||
session.install("maturin")
|
session.install(".[dev]")
|
||||||
session.run_always("maturin", "develop")
|
|
||||||
session.run("pytest")
|
session.run("pytest")
|
||||||
|
|
||||||
|
|
||||||
@nox.session
|
@nox.session
|
||||||
def bench(session):
|
def bench(session: nox.Session):
|
||||||
session.install("-rrequirements-dev.txt")
|
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||||
session.install(".")
|
session.install(".[dev]")
|
||||||
session.run("pytest", "--benchmark-enable")
|
session.run("pytest", "--benchmark-enable")
|
||||||
|
|
|
@ -15,6 +15,8 @@ classifiers = [
|
||||||
"Operating System :: MacOS :: MacOS X",
|
"Operating System :: MacOS :: MacOS X",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest", "pytest-benchmark"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = "--benchmark-disable"
|
addopts = "--benchmark-disable"
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
pytest>=3.5.0
|
|
||||||
pytest-benchmark>=3.1.1
|
|
|
@ -36,9 +36,8 @@
|
||||||
---
|
---
|
||||||
|
|
||||||
[Appendix A: Migration guide](migration.md)
|
[Appendix A: Migration guide](migration.md)
|
||||||
[Appendix B: PyO3 and rust-cpython](rust_cpython.md)
|
[Appendix B: Trait bounds](trait_bounds.md)
|
||||||
[Appendix C: Trait bounds](trait_bounds.md)
|
[Appendix C: Python typing hints](python_typing_hints.md)
|
||||||
[Appendix D: Python typing hints](python_typing_hints.md)
|
|
||||||
[CHANGELOG](changelog.md)
|
[CHANGELOG](changelog.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
@ -337,10 +337,27 @@ impl SubSubClass {
|
||||||
let super_ = self_.into_super(); // Get PyRef<'_, SubClass>
|
let super_ = self_.into_super(); // Get PyRef<'_, SubClass>
|
||||||
SubClass::method2(super_).map(|x| x * v)
|
SubClass::method2(super_).map(|x| x * v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
fn factory_method(py: Python<'_>, val: usize) -> PyResult<PyObject> {
|
||||||
|
let base = PyClassInitializer::from(BaseClass::new());
|
||||||
|
let sub = base.add_subclass(SubClass { val2: val });
|
||||||
|
if val % 2 == 0 {
|
||||||
|
Ok(Py::new(py, sub)?.to_object(py))
|
||||||
|
} else {
|
||||||
|
let sub_sub = sub.add_subclass(SubSubClass { val3: val });
|
||||||
|
Ok(Py::new(py, sub_sub)?.to_object(py))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
# Python::with_gil(|py| {
|
# Python::with_gil(|py| {
|
||||||
# let subsub = pyo3::PyCell::new(py, SubSubClass::new()).unwrap();
|
# let subsub = pyo3::PyCell::new(py, SubSubClass::new()).unwrap();
|
||||||
# pyo3::py_run!(py, subsub, "assert subsub.method3() == 3000")
|
# pyo3::py_run!(py, subsub, "assert subsub.method3() == 3000");
|
||||||
|
# let subsub = SubSubClass::factory_method(py, 2).unwrap();
|
||||||
|
# let subsubsub = SubSubClass::factory_method(py, 3).unwrap();
|
||||||
|
# let cls = py.get_type::<SubSubClass>();
|
||||||
|
# pyo3::py_run!(py, subsub cls, "assert not isinstance(subsub, cls)");
|
||||||
|
# pyo3::py_run!(py, subsubsub cls, "assert isinstance(subsubsub, cls)");
|
||||||
# });
|
# });
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ The table below contains the Python type and the corresponding function argument
|
||||||
| Python | Rust | Rust (Python-native) |
|
| Python | Rust | Rust (Python-native) |
|
||||||
| ------------- |:-------------------------------:|:--------------------:|
|
| ------------- |:-------------------------------:|:--------------------:|
|
||||||
| `object` | - | `&PyAny` |
|
| `object` | - | `&PyAny` |
|
||||||
| `str` | `String`, `Cow<str>`, `&str`, `OsString`, `PathBuf`, `Path` | `&PyString`, `&PyUnicode` |
|
| `str` | `String`, `Cow<str>`, `&str`, `char`, `OsString`, `PathBuf`, `Path` | `&PyString`, `&PyUnicode` |
|
||||||
| `bytes` | `Vec<u8>`, `&[u8]`, `Cow<[u8]>` | `&PyBytes` |
|
| `bytes` | `Vec<u8>`, `&[u8]`, `Cow<[u8]>` | `&PyBytes` |
|
||||||
| `bool` | `bool` | `&PyBool` |
|
| `bool` | `bool` | `&PyBool` |
|
||||||
| `int` | `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize`, `num_bigint::BigInt`[^1], `num_bigint::BigUint`[^1] | `&PyLong` |
|
| `int` | `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize`, `num_bigint::BigInt`[^1], `num_bigint::BigUint`[^1] | `&PyLong` |
|
||||||
|
|
|
@ -109,6 +109,10 @@ Adds a dependency on [chrono](https://docs.rs/chrono). Enables a conversion from
|
||||||
- [NaiveTime](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html) -> [`PyTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTime.html)
|
- [NaiveTime](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html) -> [`PyTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTime.html)
|
||||||
- [DateTime](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html)
|
- [DateTime](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html)
|
||||||
|
|
||||||
|
### `either`
|
||||||
|
|
||||||
|
Adds a dependency on [either](https://docs.rs/either). Enables a conversions into [either](https://docs.rs/either)’s [`Either`](https://docs.rs/either/latest/either/struct.Report.html) type.
|
||||||
|
|
||||||
### `eyre`
|
### `eyre`
|
||||||
|
|
||||||
Adds a dependency on [eyre](https://docs.rs/eyre). Enables a conversion from [eyre](https://docs.rs/eyre)’s [`Report`](https://docs.rs/eyre/latest/eyre/struct.Report.html) type to [`PyErr`]({{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html), for easy error handling.
|
Adds a dependency on [eyre](https://docs.rs/eyre). Enables a conversion from [eyre](https://docs.rs/eyre)’s [`Report`](https://docs.rs/eyre/latest/eyre/struct.Report.html) type to [`PyErr`]({{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html), for easy error handling.
|
||||||
|
@ -159,3 +163,7 @@ struct User {
|
||||||
}
|
}
|
||||||
# }
|
# }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `smallvec`
|
||||||
|
|
||||||
|
Adds a dependency on [smallvec](https://docs.rs/smallvec) and enables conversions into its [`SmallVec`](https://docs.rs/smallvec/latest/smallvec/struct.SmallVec.html) type.
|
||||||
|
|
|
@ -14,13 +14,21 @@ To use PyO3, you need at least Python 3.7. While you can simply use the default
|
||||||
|
|
||||||
## Virtualenvs
|
## Virtualenvs
|
||||||
|
|
||||||
While you can use any virtualenv manager you like, we recommend the use of `pyenv` in particular if you want to develop or test for multiple different Python versions, so that is what the examples in this book will use. The installation instructions for `pyenv` can be found [here](https://github.com/pyenv/pyenv#getting-pyenv).
|
While you can use any virtualenv manager you like, we recommend the use of `pyenv` in particular if you want to develop or test for multiple different Python versions, so that is what the examples in this book will use. The installation instructions for `pyenv` can be found [here](https://github.com/pyenv/pyenv#getting-pyenv). (Note: To get the `pyenv activate` and `pyenv virtualenv` commands, you will also need to install the [`pyenv-virtualenv`](https://github.com/pyenv/pyenv-virtualenv) plugin. The [pyenv installer](https://github.com/pyenv/pyenv-installer#installation--update--uninstallation) will install both together.)
|
||||||
|
|
||||||
Note that when using `pyenv`, you should also set the following environment variable:
|
If you intend to run Python from Rust (for example in unit tests) you should set the following environment variable when installing a new Python version using `pyenv`:
|
||||||
```bash
|
```bash
|
||||||
PYTHON_CONFIGURE_OPTS="--enable-shared"
|
PYTHON_CONFIGURE_OPTS="--enable-shared"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.12
|
||||||
|
```
|
||||||
|
|
||||||
|
You can read more about `pyenv`'s configuration options [here](https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#building-with---enable-shared).
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
There are a number of build and Python package management systems such as [`setuptools-rust`](https://github.com/PyO3/setuptools-rust) or [manually](https://pyo3.rs/latest/building_and_distribution.html#manual-builds). We recommend the use of `maturin`, which you can install [here](https://maturin.rs/installation.html). It is developed to work with PyO3 and provides the most "batteries included" experience, especially if you are aiming to publish to PyPI. `maturin` is just a Python package, so you can add it in the same you already install Python packages.
|
There are a number of build and Python package management systems such as [`setuptools-rust`](https://github.com/PyO3/setuptools-rust) or [manually](https://pyo3.rs/latest/building_and_distribution.html#manual-builds). We recommend the use of `maturin`, which you can install [here](https://maturin.rs/installation.html). It is developed to work with PyO3 and provides the most "batteries included" experience, especially if you are aiming to publish to PyPI. `maturin` is just a Python package, so you can add it in the same you already install Python packages.
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
# PyO3 and rust-cpython
|
|
||||||
|
|
||||||
PyO3 began as fork of [rust-cpython](https://github.com/dgrunwald/rust-cpython) when rust-cpython wasn't maintained. Over time PyO3 has become fundamentally different from rust-cpython.
|
|
||||||
|
|
||||||
## Macros
|
|
||||||
|
|
||||||
While rust-cpython has a `macro_rules!` based DSL for declaring modules and classes, PyO3 uses proc macros. PyO3 also doesn't change your struct and functions so you can still use them as normal Rust functions.
|
|
||||||
|
|
||||||
**rust-cpython**
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
py_class!(class MyClass |py| {
|
|
||||||
data number: i32;
|
|
||||||
def __new__(_cls, arg: i32) -> PyResult<MyClass> {
|
|
||||||
MyClass::create_instance(py, arg)
|
|
||||||
}
|
|
||||||
def half(&self) -> PyResult<i32> {
|
|
||||||
Ok(self.number(py) / 2)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**PyO3**
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use pyo3::prelude::*;
|
|
||||||
|
|
||||||
#[pyclass]
|
|
||||||
struct MyClass {
|
|
||||||
num: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pymethods]
|
|
||||||
impl MyClass {
|
|
||||||
#[new]
|
|
||||||
fn new(num: u32) -> Self {
|
|
||||||
MyClass { num }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn half(&self) -> PyResult<u32> {
|
|
||||||
Ok(self.num / 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Ownership and lifetimes
|
|
||||||
|
|
||||||
While in rust-cpython you always own Python objects, PyO3 allows efficient *borrowed objects*
|
|
||||||
and most APIs work with references.
|
|
||||||
|
|
||||||
Here is an example of the PyList API:
|
|
||||||
|
|
||||||
**rust-cpython**
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
impl PyList {
|
|
||||||
|
|
||||||
fn new(py: Python<'_>) -> PyList {...}
|
|
||||||
|
|
||||||
fn get_item(&self, py: Python<'_>, index: isize) -> PyObject {...}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**PyO3**
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
impl PyList {
|
|
||||||
|
|
||||||
fn new(py: Python<'_>) -> &PyList {...}
|
|
||||||
|
|
||||||
fn get_item(&self, index: isize) -> &PyAny {...}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In PyO3, all object references are bounded by the GIL lifetime.
|
|
||||||
So owned Python objects are not required, and it is safe to have functions like `fn py<'p>(&'p self) -> Python<'p> {}`.
|
|
||||||
|
|
||||||
## Error handling
|
|
||||||
|
|
||||||
rust-cpython requires a `Python` parameter for constructing a `PyErr`, so error handling ergonomics is pretty bad. It is not possible to use `?` with Rust errors.
|
|
||||||
|
|
||||||
PyO3 on other hand does not require `Python` for constructing a `PyErr`, it is only required if you want to raise an exception in Python with the `PyErr::restore()` method. Due to various `std::convert::From<E> for PyErr` implementations for Rust standard error types `E`, propagating `?` is supported automatically.
|
|
204
noxfile.py
204
noxfile.py
|
@ -1,3 +1,4 @@
|
||||||
|
from contextlib import contextmanager
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -7,11 +8,20 @@ import tempfile
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple
|
||||||
|
|
||||||
import nox
|
import nox
|
||||||
|
import nox.command
|
||||||
|
|
||||||
nox.options.sessions = ["test", "clippy", "fmt", "docs"]
|
try:
|
||||||
|
import tomllib as toml
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
import toml
|
||||||
|
except ImportError:
|
||||||
|
toml = None
|
||||||
|
|
||||||
|
nox.options.sessions = ["test", "clippy", "rustfmt", "ruff", "docs"]
|
||||||
|
|
||||||
|
|
||||||
PYO3_DIR = Path(__file__).parent
|
PYO3_DIR = Path(__file__).parent
|
||||||
|
@ -66,22 +76,17 @@ def coverage(session: nox.Session) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@nox.session
|
@nox.session(venv_backend="none")
|
||||||
def fmt(session: nox.Session):
|
def rustfmt(session: nox.Session):
|
||||||
fmt_rust(session)
|
|
||||||
fmt_py(session)
|
|
||||||
|
|
||||||
|
|
||||||
@nox.session(name="fmt-rust", venv_backend="none")
|
|
||||||
def fmt_rust(session: nox.Session):
|
|
||||||
_run_cargo(session, "fmt", "--all", "--check")
|
_run_cargo(session, "fmt", "--all", "--check")
|
||||||
_run_cargo(session, "fmt", _FFI_CHECK, "--all", "--check")
|
_run_cargo(session, "fmt", _FFI_CHECK, "--all", "--check")
|
||||||
|
|
||||||
|
|
||||||
@nox.session(name="fmt-py")
|
@nox.session(name="ruff")
|
||||||
def fmt_py(session: nox.Session):
|
def ruff(session: nox.Session):
|
||||||
session.install("black==22.3.0")
|
session.install("ruff")
|
||||||
_run(session, "black", ".", "--check")
|
_run(session, "ruff", "format", ".", "--check")
|
||||||
|
_run(session, "ruff", "check", ".")
|
||||||
|
|
||||||
|
|
||||||
@nox.session(name="clippy", venv_backend="none")
|
@nox.session(name="clippy", venv_backend="none")
|
||||||
|
@ -105,7 +110,7 @@ def _clippy(session: nox.Session, *, env: Dict[str, str] = None) -> bool:
|
||||||
"--deny=warnings",
|
"--deny=warnings",
|
||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
except Exception:
|
except nox.command.CommandFailed:
|
||||||
success = False
|
success = False
|
||||||
return success
|
return success
|
||||||
|
|
||||||
|
@ -221,7 +226,7 @@ def contributors(session: nox.Session) -> None:
|
||||||
for commit in body["commits"]:
|
for commit in body["commits"]:
|
||||||
try:
|
try:
|
||||||
authors.add(commit["author"]["login"])
|
authors.add(commit["author"]["login"])
|
||||||
except:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if "next" in resp.links:
|
if "next" in resp.links:
|
||||||
|
@ -261,6 +266,7 @@ def build_emscripten(session: nox.Session):
|
||||||
"make",
|
"make",
|
||||||
"-C",
|
"-C",
|
||||||
str(info.emscripten_dir),
|
str(info.emscripten_dir),
|
||||||
|
f"PYTHON={sys.executable}",
|
||||||
f"BUILDROOT={info.builddir}",
|
f"BUILDROOT={info.builddir}",
|
||||||
f"PYMAJORMINORMICRO={info.pymajorminormicro}",
|
f"PYMAJORMINORMICRO={info.pymajorminormicro}",
|
||||||
f"PYPRERELEASE={info.pydev}",
|
f"PYPRERELEASE={info.pydev}",
|
||||||
|
@ -469,10 +475,8 @@ def check_changelog(session: nox.Session):
|
||||||
def set_minimal_package_versions(session: nox.Session):
|
def set_minimal_package_versions(session: nox.Session):
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
try:
|
if toml is None:
|
||||||
import tomllib as toml
|
session.error("requires Python 3.11 or `toml` to be installed")
|
||||||
except ImportError:
|
|
||||||
import toml
|
|
||||||
|
|
||||||
projects = (
|
projects = (
|
||||||
None,
|
None,
|
||||||
|
@ -484,8 +488,8 @@ def set_minimal_package_versions(session: nox.Session):
|
||||||
min_pkg_versions = {
|
min_pkg_versions = {
|
||||||
"rust_decimal": "1.26.1",
|
"rust_decimal": "1.26.1",
|
||||||
"csv": "1.1.6",
|
"csv": "1.1.6",
|
||||||
"indexmap": "1.9.3",
|
"indexmap": "1.6.2",
|
||||||
"hashbrown": "0.12.3",
|
"hashbrown": "0.9.1",
|
||||||
"log": "0.4.17",
|
"log": "0.4.17",
|
||||||
"once_cell": "1.17.2",
|
"once_cell": "1.17.2",
|
||||||
"rayon": "1.6.1",
|
"rayon": "1.6.1",
|
||||||
|
@ -494,6 +498,10 @@ def set_minimal_package_versions(session: nox.Session):
|
||||||
"proptest": "1.0.0",
|
"proptest": "1.0.0",
|
||||||
"chrono": "0.4.25",
|
"chrono": "0.4.25",
|
||||||
"byteorder": "1.4.3",
|
"byteorder": "1.4.3",
|
||||||
|
"crossbeam-channel": "0.5.8",
|
||||||
|
"crossbeam-deque": "0.8.3",
|
||||||
|
"crossbeam-epoch": "0.9.15",
|
||||||
|
"crossbeam-utils": "0.8.16",
|
||||||
}
|
}
|
||||||
|
|
||||||
# run cargo update first to ensure that everything is at highest
|
# run cargo update first to ensure that everything is at highest
|
||||||
|
@ -552,6 +560,101 @@ def ffi_check(session: nox.Session):
|
||||||
_run_cargo(session, "run", _FFI_CHECK)
|
_run_cargo(session, "run", _FFI_CHECK)
|
||||||
|
|
||||||
|
|
||||||
|
@nox.session(name="test-version-limits")
|
||||||
|
def test_version_limits(session: nox.Session):
|
||||||
|
env = os.environ.copy()
|
||||||
|
with _config_file() as config_file:
|
||||||
|
env["PYO3_CONFIG_FILE"] = config_file.name
|
||||||
|
|
||||||
|
assert "3.6" not in PY_VERSIONS
|
||||||
|
config_file.set("CPython", "3.6")
|
||||||
|
_run_cargo(session, "check", env=env, expect_error=True)
|
||||||
|
|
||||||
|
assert "3.13" not in PY_VERSIONS
|
||||||
|
config_file.set("CPython", "3.13")
|
||||||
|
_run_cargo(session, "check", env=env, expect_error=True)
|
||||||
|
|
||||||
|
# 3.13 CPython should build with forward compatibility
|
||||||
|
env["PYO3_USE_ABI3_FORWARD_COMPATIBILITY"] = "1"
|
||||||
|
_run_cargo(session, "check", env=env)
|
||||||
|
|
||||||
|
assert "3.6" not in PYPY_VERSIONS
|
||||||
|
config_file.set("PyPy", "3.6")
|
||||||
|
_run_cargo(session, "check", env=env, expect_error=True)
|
||||||
|
|
||||||
|
assert "3.11" not in PYPY_VERSIONS
|
||||||
|
config_file.set("PyPy", "3.11")
|
||||||
|
_run_cargo(session, "check", env=env, expect_error=True)
|
||||||
|
|
||||||
|
|
||||||
|
@nox.session(name="check-feature-powerset", venv_backend="none")
|
||||||
|
def check_feature_powerset(session: nox.Session):
|
||||||
|
if toml is None:
|
||||||
|
session.error("requires Python 3.11 or `toml` to be installed")
|
||||||
|
|
||||||
|
with (PYO3_DIR / "Cargo.toml").open("rb") as cargo_toml_file:
|
||||||
|
cargo_toml = toml.load(cargo_toml_file)
|
||||||
|
|
||||||
|
EXCLUDED_FROM_FULL = {
|
||||||
|
"nightly",
|
||||||
|
"extension-module",
|
||||||
|
"full",
|
||||||
|
"default",
|
||||||
|
"auto-initialize",
|
||||||
|
"generate-import-lib",
|
||||||
|
"multiple-pymethods", # TODO add this after MSRV 1.62
|
||||||
|
}
|
||||||
|
|
||||||
|
features = cargo_toml["features"]
|
||||||
|
|
||||||
|
full_feature = set(features["full"])
|
||||||
|
abi3_features = {feature for feature in features if feature.startswith("abi3")}
|
||||||
|
abi3_version_features = abi3_features - {"abi3"}
|
||||||
|
|
||||||
|
expected_full_feature = features.keys() - EXCLUDED_FROM_FULL - abi3_features
|
||||||
|
|
||||||
|
uncovered_features = expected_full_feature - full_feature
|
||||||
|
if uncovered_features:
|
||||||
|
session.error(
|
||||||
|
f"some features missing from `full` meta feature: {uncovered_features}"
|
||||||
|
)
|
||||||
|
|
||||||
|
experimental_features = {
|
||||||
|
feature for feature in features if feature.startswith("experimental-")
|
||||||
|
}
|
||||||
|
full_without_experimental = full_feature - experimental_features
|
||||||
|
|
||||||
|
if len(experimental_features) >= 2:
|
||||||
|
# justification: we always assume that feature within these groups are
|
||||||
|
# mutually exclusive to simplify CI
|
||||||
|
features_to_group = [
|
||||||
|
full_without_experimental,
|
||||||
|
experimental_features,
|
||||||
|
]
|
||||||
|
elif len(experimental_features) == 1:
|
||||||
|
# no need to make an experimental features group
|
||||||
|
features_to_group = [full_without_experimental]
|
||||||
|
else:
|
||||||
|
session.error("no experimental features exist; please simplify the noxfile")
|
||||||
|
|
||||||
|
features_to_skip = [
|
||||||
|
*EXCLUDED_FROM_FULL,
|
||||||
|
*abi3_version_features,
|
||||||
|
]
|
||||||
|
|
||||||
|
comma_join = ",".join
|
||||||
|
_run_cargo(
|
||||||
|
session,
|
||||||
|
"hack",
|
||||||
|
"--feature-powerset",
|
||||||
|
'--optional-deps=""',
|
||||||
|
f'--skip="{comma_join(features_to_skip)}"',
|
||||||
|
*(f"--group-features={comma_join(group)}" for group in features_to_group),
|
||||||
|
"check",
|
||||||
|
"--all-targets",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _build_docs_for_ffi_check(session: nox.Session) -> None:
|
def _build_docs_for_ffi_check(session: nox.Session) -> None:
|
||||||
# pyo3-ffi-check needs to scrape docs of pyo3-ffi
|
# pyo3-ffi-check needs to scrape docs of pyo3-ffi
|
||||||
_run_cargo(session, "doc", _FFI_CHECK, "-p", "pyo3-ffi", "--no-deps")
|
_run_cargo(session, "doc", _FFI_CHECK, "-p", "pyo3-ffi", "--no-deps")
|
||||||
|
@ -640,7 +743,13 @@ def _run(session: nox.Session, *args: str, **kwargs: Any) -> None:
|
||||||
print("::endgroup::", file=sys.stderr)
|
print("::endgroup::", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
def _run_cargo(session: nox.Session, *args: str, **kwargs: Any) -> None:
|
def _run_cargo(
|
||||||
|
session: nox.Session, *args: str, expect_error: bool = False, **kwargs: Any
|
||||||
|
) -> None:
|
||||||
|
if expect_error:
|
||||||
|
if "success_codes" in kwargs:
|
||||||
|
raise ValueError("expect_error overrides success_codes")
|
||||||
|
kwargs["success_codes"] = [101]
|
||||||
_run(session, "cargo", *args, **kwargs, external=True)
|
_run(session, "cargo", *args, **kwargs, external=True)
|
||||||
|
|
||||||
|
|
||||||
|
@ -688,24 +797,14 @@ def _get_output(*args: str) -> str:
|
||||||
def _for_all_version_configs(
|
def _for_all_version_configs(
|
||||||
session: nox.Session, job: Callable[[Dict[str, str]], None]
|
session: nox.Session, job: Callable[[Dict[str, str]], None]
|
||||||
) -> None:
|
) -> None:
|
||||||
with tempfile.NamedTemporaryFile("r+") as config:
|
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env["PYO3_CONFIG_FILE"] = config.name
|
with _config_file() as config_file:
|
||||||
|
env["PYO3_CONFIG_FILE"] = config_file.name
|
||||||
def _job_with_config(implementation, version) -> bool:
|
|
||||||
config.seek(0)
|
|
||||||
config.truncate(0)
|
|
||||||
config.write(
|
|
||||||
f"""\
|
|
||||||
implementation={implementation}
|
|
||||||
version={version}
|
|
||||||
suppress_build_script_link_lines=true
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
config.flush()
|
|
||||||
|
|
||||||
|
def _job_with_config(implementation, version):
|
||||||
session.log(f"{implementation} {version}")
|
session.log(f"{implementation} {version}")
|
||||||
return job(env)
|
config_file.set(implementation, version)
|
||||||
|
job(env)
|
||||||
|
|
||||||
for version in PY_VERSIONS:
|
for version in PY_VERSIONS:
|
||||||
_job_with_config("CPython", version)
|
_job_with_config("CPython", version)
|
||||||
|
@ -714,5 +813,34 @@ suppress_build_script_link_lines=true
|
||||||
_job_with_config("PyPy", version)
|
_job_with_config("PyPy", version)
|
||||||
|
|
||||||
|
|
||||||
|
class _ConfigFile:
|
||||||
|
def __init__(self, config_file) -> None:
|
||||||
|
self._config_file = config_file
|
||||||
|
|
||||||
|
def set(self, implementation: str, version: str) -> None:
|
||||||
|
"""Set the contents of this config file to the given implementation and version."""
|
||||||
|
self._config_file.seek(0)
|
||||||
|
self._config_file.truncate(0)
|
||||||
|
self._config_file.write(
|
||||||
|
f"""\
|
||||||
|
implementation={implementation}
|
||||||
|
version={version}
|
||||||
|
suppress_build_script_link_lines=true
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
self._config_file.flush()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._config_file.name
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _config_file() -> Iterator[_ConfigFile]:
|
||||||
|
"""Creates a temporary config file which can be repeatedly set to different values."""
|
||||||
|
with tempfile.NamedTemporaryFile("r+") as config:
|
||||||
|
yield _ConfigFile(config)
|
||||||
|
|
||||||
|
|
||||||
_BENCHES = "--manifest-path=pyo3-benches/Cargo.toml"
|
_BENCHES = "--manifest-path=pyo3-benches/Cargo.toml"
|
||||||
_FFI_CHECK = "--manifest-path=pyo3-ffi-check/Cargo.toml"
|
_FFI_CHECK = "--manifest-path=pyo3-ffi-check/Cargo.toml"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pyo3-build-config"
|
name = "pyo3-build-config"
|
||||||
version = "0.20.0"
|
version = "0.20.3"
|
||||||
description = "Build configuration for the PyO3 ecosystem"
|
description = "Build configuration for the PyO3 ecosystem"
|
||||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||||
|
@ -35,7 +35,8 @@ abi3-py37 = ["abi3-py38"]
|
||||||
abi3-py38 = ["abi3-py39"]
|
abi3-py38 = ["abi3-py39"]
|
||||||
abi3-py39 = ["abi3-py310"]
|
abi3-py39 = ["abi3-py310"]
|
||||||
abi3-py310 = ["abi3-py311"]
|
abi3-py310 = ["abi3-py311"]
|
||||||
abi3-py311 = ["abi3"]
|
abi3-py311 = ["abi3-py312"]
|
||||||
|
abi3-py312 = ["abi3"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["resolve-config"]
|
features = ["resolve-config"]
|
||||||
|
|
|
@ -12,15 +12,21 @@ macro_rules! ensure {
|
||||||
($condition:expr, $($args: tt)+) => { if !($condition) { bail!($($args)+) } };
|
($condition:expr, $($args: tt)+) => { if !($condition) { bail!($($args)+) } };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show warning. If needed, please extend this macro to support arguments.
|
/// Show warning.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
macro_rules! warn {
|
macro_rules! warn {
|
||||||
($msg: literal) => {
|
($($args: tt)+) => {
|
||||||
println!(concat!("cargo:warning=", $msg))
|
println!("{}", $crate::format_warn!($($args)+))
|
||||||
};
|
};
|
||||||
($fmt: expr, $($args: tt)+) => {
|
}
|
||||||
println!("cargo:warning={}", format_args!($fmt, $($args)+))
|
|
||||||
|
/// Format warning into string.
|
||||||
|
#[macro_export]
|
||||||
|
#[doc(hidden)]
|
||||||
|
macro_rules! format_warn {
|
||||||
|
($($args: tt)+) => {
|
||||||
|
format!("cargo:warning={}", format_args!($($args)+))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ mod import_lib;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
convert::AsRef,
|
|
||||||
env,
|
env,
|
||||||
ffi::{OsStr, OsString},
|
ffi::{OsStr, OsString},
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
|
@ -27,14 +26,14 @@ use target_lexicon::{Environment, OperatingSystem};
|
||||||
use crate::{
|
use crate::{
|
||||||
bail, ensure,
|
bail, ensure,
|
||||||
errors::{Context, Error, Result},
|
errors::{Context, Error, Result},
|
||||||
warn,
|
format_warn, warn,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Minimum Python version PyO3 supports.
|
/// Minimum Python version PyO3 supports.
|
||||||
const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 7 };
|
const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 7 };
|
||||||
|
|
||||||
/// Maximum Python version that can be used as minimum required Python version with abi3.
|
/// Maximum Python version that can be used as minimum required Python version with abi3.
|
||||||
const ABI3_MAX_MINOR: u8 = 11;
|
const ABI3_MAX_MINOR: u8 = 12;
|
||||||
|
|
||||||
/// Gets an environment variable owned by cargo.
|
/// Gets an environment variable owned by cargo.
|
||||||
///
|
///
|
||||||
|
@ -155,30 +154,41 @@ pub struct InterpreterConfig {
|
||||||
impl InterpreterConfig {
|
impl InterpreterConfig {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn emit_pyo3_cfgs(&self) {
|
pub fn emit_pyo3_cfgs(&self) {
|
||||||
|
for cfg in self.build_script_outputs() {
|
||||||
|
println!("{}", cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn build_script_outputs(&self) -> Vec<String> {
|
||||||
// This should have been checked during pyo3-build-config build time.
|
// This should have been checked during pyo3-build-config build time.
|
||||||
assert!(self.version >= MINIMUM_SUPPORTED_VERSION);
|
assert!(self.version >= MINIMUM_SUPPORTED_VERSION);
|
||||||
|
|
||||||
|
let mut out = vec![];
|
||||||
|
|
||||||
// pyo3-build-config was released when Python 3.6 was supported, so minimum flag to emit is
|
// pyo3-build-config was released when Python 3.6 was supported, so minimum flag to emit is
|
||||||
// Py_3_6 (to avoid silently breaking users who depend on this cfg).
|
// Py_3_6 (to avoid silently breaking users who depend on this cfg).
|
||||||
for i in 6..=self.version.minor {
|
for i in 6..=self.version.minor {
|
||||||
println!("cargo:rustc-cfg=Py_3_{}", i);
|
out.push(format!("cargo:rustc-cfg=Py_3_{}", i));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.implementation.is_pypy() {
|
if self.implementation.is_pypy() {
|
||||||
println!("cargo:rustc-cfg=PyPy");
|
out.push("cargo:rustc-cfg=PyPy".to_owned());
|
||||||
if self.abi3 {
|
if self.abi3 {
|
||||||
warn!(
|
out.push(format_warn!(
|
||||||
"PyPy does not yet support abi3 so the build artifacts will be version-specific. \
|
"PyPy does not yet support abi3 so the build artifacts will be version-specific. \
|
||||||
See https://foss.heptapod.net/pypy/pypy/-/issues/3397 for more information."
|
See https://foss.heptapod.net/pypy/pypy/-/issues/3397 for more information."
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
} else if self.abi3 {
|
} else if self.abi3 {
|
||||||
println!("cargo:rustc-cfg=Py_LIMITED_API");
|
out.push("cargo:rustc-cfg=Py_LIMITED_API".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
for flag in &self.build_flags.0 {
|
for flag in &self.build_flags.0 {
|
||||||
println!("cargo:rustc-cfg=py_sys_config=\"{}\"", flag);
|
out.push(format!("cargo:rustc-cfg=py_sys_config=\"{}\"", flag));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -697,6 +707,7 @@ fn have_python_interpreter() -> bool {
|
||||||
/// Must be called from a PyO3 crate build script.
|
/// Must be called from a PyO3 crate build script.
|
||||||
fn is_abi3() -> bool {
|
fn is_abi3() -> bool {
|
||||||
cargo_env_var("CARGO_FEATURE_ABI3").is_some()
|
cargo_env_var("CARGO_FEATURE_ABI3").is_some()
|
||||||
|
|| env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").map_or(false, |os_str| os_str == "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the minimum supported Python version from PyO3 `abi3-py*` features.
|
/// Gets the minimum supported Python version from PyO3 `abi3-py*` features.
|
||||||
|
@ -1011,12 +1022,12 @@ impl BuildFlags {
|
||||||
Self(
|
Self(
|
||||||
BuildFlags::ALL
|
BuildFlags::ALL
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
|
||||||
.filter(|flag| {
|
.filter(|flag| {
|
||||||
config_map
|
config_map
|
||||||
.get_value(&flag.to_string())
|
.get_value(&flag.to_string())
|
||||||
.map_or(false, |value| value == "1")
|
.map_or(false, |value| value == "1")
|
||||||
})
|
})
|
||||||
|
.cloned()
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
.fixup()
|
.fixup()
|
||||||
|
@ -1357,9 +1368,13 @@ fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result<In
|
||||||
let version = cross_compile_config
|
let version = cross_compile_config
|
||||||
.version
|
.version
|
||||||
.or_else(get_abi3_version)
|
.or_else(get_abi3_version)
|
||||||
.ok_or(
|
.ok_or_else(||
|
||||||
|
format!(
|
||||||
"PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified \
|
"PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified \
|
||||||
when cross-compiling and PYO3_CROSS_LIB_DIR is not set.",
|
when cross-compiling and PYO3_CROSS_LIB_DIR is not set.\n\
|
||||||
|
= help: see the PyO3 user guide for more information: https://pyo3.rs/v{}/building_and_distribution.html#cross-compiling",
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let abi3 = is_abi3();
|
let abi3 = is_abi3();
|
||||||
|
@ -1776,7 +1791,6 @@ fn unescape(escaped: &str) -> Vec<u8> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::iter::FromIterator;
|
|
||||||
use target_lexicon::triple;
|
use target_lexicon::triple;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -2581,4 +2595,114 @@ mod tests {
|
||||||
.expect("failed to run Python script");
|
.expect("failed to run Python script");
|
||||||
assert_eq!(out.trim_end(), "42");
|
assert_eq!(out.trim_end(), "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_script_outputs_base() {
|
||||||
|
let interpreter_config = InterpreterConfig {
|
||||||
|
implementation: PythonImplementation::CPython,
|
||||||
|
version: PythonVersion { major: 3, minor: 8 },
|
||||||
|
shared: true,
|
||||||
|
abi3: false,
|
||||||
|
lib_name: Some("python3".into()),
|
||||||
|
lib_dir: None,
|
||||||
|
executable: None,
|
||||||
|
pointer_width: None,
|
||||||
|
build_flags: BuildFlags::default(),
|
||||||
|
suppress_build_script_link_lines: false,
|
||||||
|
extra_build_script_lines: vec![],
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
interpreter_config.build_script_outputs(),
|
||||||
|
[
|
||||||
|
"cargo:rustc-cfg=Py_3_6".to_owned(),
|
||||||
|
"cargo:rustc-cfg=Py_3_7".to_owned(),
|
||||||
|
"cargo:rustc-cfg=Py_3_8".to_owned(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
let interpreter_config = InterpreterConfig {
|
||||||
|
implementation: PythonImplementation::PyPy,
|
||||||
|
..interpreter_config
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
interpreter_config.build_script_outputs(),
|
||||||
|
[
|
||||||
|
"cargo:rustc-cfg=Py_3_6".to_owned(),
|
||||||
|
"cargo:rustc-cfg=Py_3_7".to_owned(),
|
||||||
|
"cargo:rustc-cfg=Py_3_8".to_owned(),
|
||||||
|
"cargo:rustc-cfg=PyPy".to_owned(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_script_outputs_abi3() {
|
||||||
|
let interpreter_config = InterpreterConfig {
|
||||||
|
implementation: PythonImplementation::CPython,
|
||||||
|
version: PythonVersion { major: 3, minor: 7 },
|
||||||
|
shared: true,
|
||||||
|
abi3: true,
|
||||||
|
lib_name: Some("python3".into()),
|
||||||
|
lib_dir: None,
|
||||||
|
executable: None,
|
||||||
|
pointer_width: None,
|
||||||
|
build_flags: BuildFlags::default(),
|
||||||
|
suppress_build_script_link_lines: false,
|
||||||
|
extra_build_script_lines: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
interpreter_config.build_script_outputs(),
|
||||||
|
[
|
||||||
|
"cargo:rustc-cfg=Py_3_6".to_owned(),
|
||||||
|
"cargo:rustc-cfg=Py_3_7".to_owned(),
|
||||||
|
"cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
let interpreter_config = InterpreterConfig {
|
||||||
|
implementation: PythonImplementation::PyPy,
|
||||||
|
..interpreter_config
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
interpreter_config.build_script_outputs(),
|
||||||
|
[
|
||||||
|
"cargo:rustc-cfg=Py_3_6".to_owned(),
|
||||||
|
"cargo:rustc-cfg=Py_3_7".to_owned(),
|
||||||
|
"cargo:rustc-cfg=PyPy".to_owned(),
|
||||||
|
"cargo:warning=PyPy does not yet support abi3 so the build artifacts \
|
||||||
|
will be version-specific. See https://foss.heptapod.net/pypy/pypy/-/issues/3397 \
|
||||||
|
for more information."
|
||||||
|
.to_owned(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_script_outputs_debug() {
|
||||||
|
let mut build_flags = BuildFlags::default();
|
||||||
|
build_flags.0.insert(BuildFlag::Py_DEBUG);
|
||||||
|
let interpreter_config = InterpreterConfig {
|
||||||
|
implementation: PythonImplementation::CPython,
|
||||||
|
version: PythonVersion { major: 3, minor: 7 },
|
||||||
|
shared: true,
|
||||||
|
abi3: false,
|
||||||
|
lib_name: Some("python3".into()),
|
||||||
|
lib_dir: None,
|
||||||
|
executable: None,
|
||||||
|
pointer_width: None,
|
||||||
|
build_flags,
|
||||||
|
suppress_build_script_link_lines: false,
|
||||||
|
extra_build_script_lines: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
interpreter_config.build_script_outputs(),
|
||||||
|
[
|
||||||
|
"cargo:rustc-cfg=Py_3_6".to_owned(),
|
||||||
|
"cargo:rustc-cfg=Py_3_7".to_owned(),
|
||||||
|
"cargo:rustc-cfg=py_sys_config=\"Py_DEBUG\"".to_owned(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,9 @@ use target_lexicon::OperatingSystem;
|
||||||
/// For examples of how to use these attributes, [see PyO3's guide](https://pyo3.rs/latest/building_and_distribution/multiple_python_versions.html).
|
/// For examples of how to use these attributes, [see PyO3's guide](https://pyo3.rs/latest/building_and_distribution/multiple_python_versions.html).
|
||||||
#[cfg(feature = "resolve-config")]
|
#[cfg(feature = "resolve-config")]
|
||||||
pub fn use_pyo3_cfgs() {
|
pub fn use_pyo3_cfgs() {
|
||||||
get().emit_pyo3_cfgs();
|
for cargo_command in get().build_script_outputs() {
|
||||||
|
println!("{}", cargo_command)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds linker arguments suitable for PyO3's `extension-module` feature.
|
/// Adds linker arguments suitable for PyO3's `extension-module` feature.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pyo3-ffi"
|
name = "pyo3-ffi"
|
||||||
version = "0.20.0"
|
version = "0.20.3"
|
||||||
description = "Python-API bindings for the PyO3 ecosystem"
|
description = "Python-API bindings for the PyO3 ecosystem"
|
||||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||||
|
@ -31,11 +31,14 @@ abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37"]
|
||||||
abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38"]
|
abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38"]
|
||||||
abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39"]
|
abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39"]
|
||||||
abi3-py310 = ["abi3-py311", "pyo3-build-config/abi3-py310"]
|
abi3-py310 = ["abi3-py311", "pyo3-build-config/abi3-py310"]
|
||||||
abi3-py311 = ["abi3", "pyo3-build-config/abi3-py311"]
|
abi3-py311 = ["abi3-py312", "pyo3-build-config/abi3-py311"]
|
||||||
|
abi3-py312 = ["abi3", "pyo3-build-config/abi3-py312"]
|
||||||
|
|
||||||
# Automatically generates `python3.dll` import libraries for Windows targets.
|
# Automatically generates `python3.dll` import libraries for Windows targets.
|
||||||
generate-import-lib = ["pyo3-build-config/python3-dll-a"]
|
generate-import-lib = ["pyo3-build-config/python3-dll-a"]
|
||||||
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
pyo3-build-config = { path = "../pyo3-build-config", version = "0.20.0", features = ["resolve-config"] }
|
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.20.3", features = ["resolve-config"] }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
|
@ -4,18 +4,76 @@ use pyo3_build_config::{
|
||||||
cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config,
|
cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config,
|
||||||
InterpreterConfig, PythonVersion,
|
InterpreterConfig, PythonVersion,
|
||||||
},
|
},
|
||||||
|
PythonImplementation,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Minimum Python version PyO3 supports.
|
/// Minimum Python version PyO3 supports.
|
||||||
const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 7 };
|
struct SupportedVersions {
|
||||||
|
min: PythonVersion,
|
||||||
|
max: PythonVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
const SUPPORTED_VERSIONS_CPYTHON: SupportedVersions = SupportedVersions {
|
||||||
|
min: PythonVersion { major: 3, minor: 7 },
|
||||||
|
max: PythonVersion {
|
||||||
|
major: 3,
|
||||||
|
minor: 12,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const SUPPORTED_VERSIONS_PYPY: SupportedVersions = SupportedVersions {
|
||||||
|
min: PythonVersion { major: 3, minor: 7 },
|
||||||
|
max: PythonVersion {
|
||||||
|
major: 3,
|
||||||
|
minor: 10,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> {
|
fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> {
|
||||||
|
// This is an undocumented env var which is only really intended to be used in CI / for testing
|
||||||
|
// and development.
|
||||||
|
if std::env::var("UNSAFE_PYO3_SKIP_VERSION_CHECK").as_deref() == Ok("1") {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
match interpreter_config.implementation {
|
||||||
|
PythonImplementation::CPython => {
|
||||||
|
let versions = SUPPORTED_VERSIONS_CPYTHON;
|
||||||
ensure!(
|
ensure!(
|
||||||
interpreter_config.version >= MINIMUM_SUPPORTED_VERSION,
|
interpreter_config.version >= versions.min,
|
||||||
"the configured Python interpreter version ({}) is lower than PyO3's minimum supported version ({})",
|
"the configured Python interpreter version ({}) is lower than PyO3's minimum supported version ({})",
|
||||||
interpreter_config.version,
|
interpreter_config.version,
|
||||||
MINIMUM_SUPPORTED_VERSION,
|
versions.min,
|
||||||
);
|
);
|
||||||
|
ensure!(
|
||||||
|
interpreter_config.version <= versions.max || env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").map_or(false, |os_str| os_str == "1"),
|
||||||
|
"the configured Python interpreter version ({}) is newer than PyO3's maximum supported version ({})\n\
|
||||||
|
= help: please check if an updated version of PyO3 is available. Current version: {}\n\
|
||||||
|
= help: set PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 to suppress this check and build anyway using the stable ABI",
|
||||||
|
interpreter_config.version,
|
||||||
|
versions.max,
|
||||||
|
std::env::var("CARGO_PKG_VERSION").unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
PythonImplementation::PyPy => {
|
||||||
|
let versions = SUPPORTED_VERSIONS_PYPY;
|
||||||
|
ensure!(
|
||||||
|
interpreter_config.version >= versions.min,
|
||||||
|
"the configured PyPy interpreter version ({}) is lower than PyO3's minimum supported version ({})",
|
||||||
|
interpreter_config.version,
|
||||||
|
versions.min,
|
||||||
|
);
|
||||||
|
// PyO3 does not support abi3, so we cannot offer forward compatibility
|
||||||
|
ensure!(
|
||||||
|
interpreter_config.version <= versions.max,
|
||||||
|
"the configured PyPy interpreter version ({}) is newer than PyO3's maximum supported version ({})\n\
|
||||||
|
= help: please check if an updated version of PyO3 is available. Current version: {}",
|
||||||
|
interpreter_config.version,
|
||||||
|
versions.max,
|
||||||
|
std::env::var("CARGO_PKG_VERSION").unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -93,7 +151,9 @@ fn configure_pyo3() -> Result<()> {
|
||||||
emit_link_config(&interpreter_config)?;
|
emit_link_config(&interpreter_config)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
interpreter_config.emit_pyo3_cfgs();
|
for cfg in interpreter_config.build_script_outputs() {
|
||||||
|
println!("{}", cfg)
|
||||||
|
}
|
||||||
|
|
||||||
// Extra lines come last, to support last write wins.
|
// Extra lines come last, to support last write wins.
|
||||||
for line in &interpreter_config.extra_build_script_lines {
|
for line in &interpreter_config.extra_build_script_lines {
|
||||||
|
@ -109,7 +169,7 @@ fn configure_pyo3() -> Result<()> {
|
||||||
fn print_config_and_exit(config: &InterpreterConfig) {
|
fn print_config_and_exit(config: &InterpreterConfig) {
|
||||||
println!("\n-- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile --");
|
println!("\n-- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile --");
|
||||||
config
|
config
|
||||||
.to_writer(&mut std::io::stdout())
|
.to_writer(std::io::stdout())
|
||||||
.expect("failed to print config to stdout");
|
.expect("failed to print config to stdout");
|
||||||
println!("\nnote: unset the PYO3_PRINT_CONFIG environment variable and retry to compile with the above config");
|
println!("\nnote: unset the PYO3_PRINT_CONFIG environment variable and retry to compile with the above config");
|
||||||
std::process::exit(101);
|
std::process::exit(101);
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
#[cfg(Py_LIMITED_API)]
|
// This header doesn't exist in CPython, but Include/cpython/code.h does. We add
|
||||||
|
// this here so that PyCodeObject has a definition under the limited API.
|
||||||
|
|
||||||
opaque_struct!(PyCodeObject);
|
opaque_struct!(PyCodeObject);
|
||||||
|
|
|
@ -65,4 +65,10 @@ pub struct _frozen {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#[cfg(not(PyPy))]
|
#[cfg(not(PyPy))]
|
||||||
pub static mut PyImport_FrozenModules: *const _frozen;
|
pub static mut PyImport_FrozenModules: *const _frozen;
|
||||||
|
#[cfg(all(not(PyPy), Py_3_11))]
|
||||||
|
pub static mut _PyImport_FrozenBootstrap: *const _frozen;
|
||||||
|
#[cfg(all(not(PyPy), Py_3_11))]
|
||||||
|
pub static mut _PyImport_FrozenStdlib: *const _frozen;
|
||||||
|
#[cfg(all(not(PyPy), Py_3_11))]
|
||||||
|
pub static mut _PyImport_FrozenTest: *const _frozen;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@ pub(crate) mod pystate;
|
||||||
pub(crate) mod pythonrun;
|
pub(crate) mod pythonrun;
|
||||||
// skipped sysmodule.h
|
// skipped sysmodule.h
|
||||||
pub(crate) mod floatobject;
|
pub(crate) mod floatobject;
|
||||||
#[cfg(not(PyPy))]
|
|
||||||
pub(crate) mod pyframe;
|
pub(crate) mod pyframe;
|
||||||
pub(crate) mod tupleobject;
|
pub(crate) mod tupleobject;
|
||||||
pub(crate) mod unicodeobject;
|
pub(crate) mod unicodeobject;
|
||||||
|
@ -60,7 +59,7 @@ pub use self::object::*;
|
||||||
pub use self::objimpl::*;
|
pub use self::objimpl::*;
|
||||||
pub use self::pydebug::*;
|
pub use self::pydebug::*;
|
||||||
pub use self::pyerrors::*;
|
pub use self::pyerrors::*;
|
||||||
#[cfg(not(PyPy))]
|
#[cfg(Py_3_11)]
|
||||||
pub use self::pyframe::*;
|
pub use self::pyframe::*;
|
||||||
#[cfg(all(Py_3_8, not(PyPy)))]
|
#[cfg(all(Py_3_8, not(PyPy)))]
|
||||||
pub use self::pylifecycle::*;
|
pub use self::pylifecycle::*;
|
||||||
|
@ -69,4 +68,5 @@ pub use self::pystate::*;
|
||||||
pub use self::pythonrun::*;
|
pub use self::pythonrun::*;
|
||||||
pub use self::tupleobject::*;
|
pub use self::tupleobject::*;
|
||||||
pub use self::unicodeobject::*;
|
pub use self::unicodeobject::*;
|
||||||
|
#[cfg(not(PyPy))]
|
||||||
pub use self::weakrefobject::*;
|
pub use self::weakrefobject::*;
|
||||||
|
|
|
@ -262,6 +262,7 @@ pub use self::boolobject::*;
|
||||||
pub use self::bytearrayobject::*;
|
pub use self::bytearrayobject::*;
|
||||||
pub use self::bytesobject::*;
|
pub use self::bytesobject::*;
|
||||||
pub use self::ceval::*;
|
pub use self::ceval::*;
|
||||||
|
#[cfg(Py_LIMITED_API)]
|
||||||
pub use self::code::*;
|
pub use self::code::*;
|
||||||
pub use self::codecs::*;
|
pub use self::codecs::*;
|
||||||
pub use self::compile::*;
|
pub use self::compile::*;
|
||||||
|
@ -326,6 +327,7 @@ mod bytesobject;
|
||||||
// skipped cellobject.h
|
// skipped cellobject.h
|
||||||
mod ceval;
|
mod ceval;
|
||||||
// skipped classobject.h
|
// skipped classobject.h
|
||||||
|
#[cfg(Py_LIMITED_API)]
|
||||||
mod code;
|
mod code;
|
||||||
mod codecs;
|
mod codecs;
|
||||||
mod compile;
|
mod compile;
|
||||||
|
|
|
@ -487,7 +487,7 @@ pub unsafe fn Py_INCREF(op: *mut PyObject) {
|
||||||
)
|
)
|
||||||
))]
|
))]
|
||||||
{
|
{
|
||||||
return _Py_IncRef(op);
|
_Py_IncRef(op);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_3_10)))]
|
#[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_3_10)))]
|
||||||
|
@ -552,7 +552,7 @@ pub unsafe fn Py_DECREF(op: *mut PyObject) {
|
||||||
)
|
)
|
||||||
))]
|
))]
|
||||||
{
|
{
|
||||||
return _Py_DecRef(op);
|
_Py_DecRef(op);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_3_10)))]
|
#[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_3_10)))]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pyo3-macros-backend"
|
name = "pyo3-macros-backend"
|
||||||
version = "0.20.0"
|
version = "0.20.3"
|
||||||
description = "Code generation for PyO3 package"
|
description = "Code generation for PyO3 package"
|
||||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||||
|
@ -14,14 +14,15 @@ edition = "2021"
|
||||||
# not to depend on proc-macro itself.
|
# not to depend on proc-macro itself.
|
||||||
# See https://github.com/PyO3/pyo3/pull/810 for more.
|
# See https://github.com/PyO3/pyo3/pull/810 for more.
|
||||||
[dependencies]
|
[dependencies]
|
||||||
quote = { version = "1", default-features = false }
|
|
||||||
proc-macro2 = { version = "1", default-features = false }
|
|
||||||
heck = "0.4"
|
heck = "0.4"
|
||||||
|
proc-macro2 = { version = "1", default-features = false }
|
||||||
|
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.20.3", features = ["resolve-config"] }
|
||||||
|
quote = { version = "1", default-features = false }
|
||||||
|
|
||||||
[dependencies.syn]
|
[dependencies.syn]
|
||||||
version = "2"
|
version = "2"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"]
|
features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"]
|
||||||
|
|
||||||
[features]
|
[lints]
|
||||||
abi3 = []
|
workspace = true
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::params::impl_arg_params;
|
||||||
use crate::pyfunction::{FunctionSignature, PyFunctionArgPyO3Attributes};
|
use crate::pyfunction::{FunctionSignature, PyFunctionArgPyO3Attributes};
|
||||||
use crate::pyfunction::{PyFunctionOptions, SignatureAttribute};
|
use crate::pyfunction::{PyFunctionOptions, SignatureAttribute};
|
||||||
use crate::quotes;
|
use crate::quotes;
|
||||||
use crate::utils::{self, PythonDoc};
|
use crate::utils::{self, is_abi3, PythonDoc};
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use quote::{quote, quote_spanned};
|
use quote::{quote, quote_spanned};
|
||||||
|
@ -78,10 +78,10 @@ pub enum FnType {
|
||||||
Setter(SelfType),
|
Setter(SelfType),
|
||||||
Fn(SelfType),
|
Fn(SelfType),
|
||||||
FnNew,
|
FnNew,
|
||||||
FnNewClass,
|
FnNewClass(Span),
|
||||||
FnClass,
|
FnClass(Span),
|
||||||
FnStatic,
|
FnStatic,
|
||||||
FnModule,
|
FnModule(Span),
|
||||||
ClassAttribute,
|
ClassAttribute,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,9 +91,9 @@ impl FnType {
|
||||||
FnType::Getter(_)
|
FnType::Getter(_)
|
||||||
| FnType::Setter(_)
|
| FnType::Setter(_)
|
||||||
| FnType::Fn(_)
|
| FnType::Fn(_)
|
||||||
| FnType::FnClass
|
| FnType::FnClass(_)
|
||||||
| FnType::FnNewClass
|
| FnType::FnNewClass(_)
|
||||||
| FnType::FnModule => true,
|
| FnType::FnModule(_) => true,
|
||||||
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => false,
|
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,14 +111,18 @@ impl FnType {
|
||||||
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => {
|
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => {
|
||||||
quote!()
|
quote!()
|
||||||
}
|
}
|
||||||
FnType::FnClass | FnType::FnNewClass => {
|
FnType::FnClass(span) | FnType::FnNewClass(span) => {
|
||||||
quote! {
|
let py = syn::Ident::new("py", Span::call_site());
|
||||||
_pyo3::types::PyType::from_type_ptr(py, _slf as *mut _pyo3::ffi::PyTypeObject),
|
let slf: Ident = syn::Ident::new("_slf", Span::call_site());
|
||||||
|
quote_spanned! { *span =>
|
||||||
|
#[allow(clippy::useless_conversion)]
|
||||||
|
::std::convert::Into::into(_pyo3::types::PyType::from_type_ptr(#py, #slf.cast())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FnType::FnModule => {
|
FnType::FnModule(span) => {
|
||||||
quote! {
|
quote_spanned! { *span =>
|
||||||
py.from_borrowed_ptr::<_pyo3::types::PyModule>(_slf),
|
#[allow(clippy::useless_conversion)]
|
||||||
|
::std::convert::Into::into(py.from_borrowed_ptr::<_pyo3::types::PyModule>(_slf)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,6 +182,7 @@ impl SelfType {
|
||||||
.map_err(::std::convert::Into::<_pyo3::PyErr>::into)
|
.map_err(::std::convert::Into::<_pyo3::PyErr>::into)
|
||||||
.and_then(
|
.and_then(
|
||||||
#[allow(clippy::useless_conversion)] // In case slf is PyCell<Self>
|
#[allow(clippy::useless_conversion)] // In case slf is PyCell<Self>
|
||||||
|
#[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] // In case slf is Py<Self> (unknown_lints can be removed when MSRV is 1.75+)
|
||||||
|cell| ::std::convert::TryFrom::try_from(cell).map_err(::std::convert::Into::into)
|
|cell| ::std::convert::TryFrom::try_from(cell).map_err(::std::convert::Into::into)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -208,8 +213,8 @@ impl CallingConvention {
|
||||||
} else if signature.python_signature.kwargs.is_some() {
|
} else if signature.python_signature.kwargs.is_some() {
|
||||||
// for functions that accept **kwargs, always prefer varargs
|
// for functions that accept **kwargs, always prefer varargs
|
||||||
Self::Varargs
|
Self::Varargs
|
||||||
} else if cfg!(not(feature = "abi3")) {
|
} else if !is_abi3() {
|
||||||
// Not available in the Stable ABI as of Python 3.10
|
// FIXME: available in the stable ABI since 3.10
|
||||||
Self::Fastcall
|
Self::Fastcall
|
||||||
} else {
|
} else {
|
||||||
Self::Varargs
|
Self::Varargs
|
||||||
|
@ -303,7 +308,7 @@ impl<'a> FnSpec<'a> {
|
||||||
FunctionSignature::from_arguments(arguments)?
|
FunctionSignature::from_arguments(arguments)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let convention = if matches!(fn_type, FnType::FnNew | FnType::FnNewClass) {
|
let convention = if matches!(fn_type, FnType::FnNew | FnType::FnNewClass(_)) {
|
||||||
CallingConvention::TpNew
|
CallingConvention::TpNew
|
||||||
} else {
|
} else {
|
||||||
CallingConvention::from_signature(&signature)
|
CallingConvention::from_signature(&signature)
|
||||||
|
@ -351,36 +356,40 @@ impl<'a> FnSpec<'a> {
|
||||||
.map(|stripped| syn::Ident::new(stripped, name.span()))
|
.map(|stripped| syn::Ident::new(stripped, name.span()))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut set_name_to_new = || {
|
||||||
|
if let Some(name) = &python_name {
|
||||||
|
bail_spanned!(name.span() => "`name` not allowed with `#[new]`");
|
||||||
|
}
|
||||||
|
*python_name = Some(syn::Ident::new("__new__", Span::call_site()));
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
let fn_type = match method_attributes.as_mut_slice() {
|
let fn_type = match method_attributes.as_mut_slice() {
|
||||||
[] => FnType::Fn(parse_receiver(
|
[] => FnType::Fn(parse_receiver(
|
||||||
"static method needs #[staticmethod] attribute",
|
"static method needs #[staticmethod] attribute",
|
||||||
)?),
|
)?),
|
||||||
[MethodTypeAttribute::StaticMethod(_)] => FnType::FnStatic,
|
[MethodTypeAttribute::StaticMethod(_)] => FnType::FnStatic,
|
||||||
[MethodTypeAttribute::ClassAttribute(_)] => FnType::ClassAttribute,
|
[MethodTypeAttribute::ClassAttribute(_)] => FnType::ClassAttribute,
|
||||||
[MethodTypeAttribute::New(_)]
|
[MethodTypeAttribute::New(_)] => {
|
||||||
| [MethodTypeAttribute::New(_), MethodTypeAttribute::ClassMethod(_)]
|
set_name_to_new()?;
|
||||||
| [MethodTypeAttribute::ClassMethod(_), MethodTypeAttribute::New(_)] => {
|
|
||||||
if let Some(name) = &python_name {
|
|
||||||
bail_spanned!(name.span() => "`name` not allowed with `#[new]`");
|
|
||||||
}
|
|
||||||
*python_name = Some(syn::Ident::new("__new__", Span::call_site()));
|
|
||||||
if matches!(method_attributes.as_slice(), [MethodTypeAttribute::New(_)]) {
|
|
||||||
FnType::FnNew
|
FnType::FnNew
|
||||||
} else {
|
|
||||||
FnType::FnNewClass
|
|
||||||
}
|
}
|
||||||
|
[MethodTypeAttribute::New(_), MethodTypeAttribute::ClassMethod(span)]
|
||||||
|
| [MethodTypeAttribute::ClassMethod(span), MethodTypeAttribute::New(_)] => {
|
||||||
|
set_name_to_new()?;
|
||||||
|
FnType::FnNewClass(*span)
|
||||||
}
|
}
|
||||||
[MethodTypeAttribute::ClassMethod(_)] => {
|
[MethodTypeAttribute::ClassMethod(_)] => {
|
||||||
// Add a helpful hint if the classmethod doesn't look like a classmethod
|
// Add a helpful hint if the classmethod doesn't look like a classmethod
|
||||||
match sig.inputs.first() {
|
let span = match sig.inputs.first() {
|
||||||
// Don't actually bother checking the type of the first argument, the compiler
|
// Don't actually bother checking the type of the first argument, the compiler
|
||||||
// will error on incorrect type.
|
// will error on incorrect type.
|
||||||
Some(syn::FnArg::Typed(_)) => {}
|
Some(syn::FnArg::Typed(first_arg)) => first_arg.ty.span(),
|
||||||
Some(syn::FnArg::Receiver(_)) | None => bail_spanned!(
|
Some(syn::FnArg::Receiver(_)) | None => bail_spanned!(
|
||||||
sig.inputs.span() => "Expected `cls: &PyType` as the first argument to `#[classmethod]`"
|
sig.paren_token.span.join() => "Expected `&PyType` or `Py<PyType>` as the first argument to `#[classmethod]`"
|
||||||
),
|
),
|
||||||
}
|
};
|
||||||
FnType::FnClass
|
FnType::FnClass(span)
|
||||||
}
|
}
|
||||||
[MethodTypeAttribute::Getter(_, name)] => {
|
[MethodTypeAttribute::Getter(_, name)] => {
|
||||||
if let Some(name) = name.take() {
|
if let Some(name) = name.take() {
|
||||||
|
@ -508,17 +517,12 @@ impl<'a> FnSpec<'a> {
|
||||||
}
|
}
|
||||||
CallingConvention::TpNew => {
|
CallingConvention::TpNew => {
|
||||||
let (arg_convert, args) = impl_arg_params(self, cls, false)?;
|
let (arg_convert, args) = impl_arg_params(self, cls, false)?;
|
||||||
let call = match &self.tp {
|
let self_arg = self.tp.self_arg(cls, ExtractErrorMode::Raise);
|
||||||
FnType::FnNew => quote! { #rust_name(#(#args),*) },
|
let call = quote! { #rust_name(#self_arg #(#args),*) };
|
||||||
FnType::FnNewClass => {
|
|
||||||
quote! { #rust_name(_pyo3::types::PyType::from_type_ptr(py, subtype), #(#args),*) }
|
|
||||||
}
|
|
||||||
x => panic!("Only `FnNew` or `FnNewClass` may use the `TpNew` calling convention. Got: {:?}", x),
|
|
||||||
};
|
|
||||||
quote! {
|
quote! {
|
||||||
unsafe fn #ident(
|
unsafe fn #ident(
|
||||||
py: _pyo3::Python<'_>,
|
py: _pyo3::Python<'_>,
|
||||||
subtype: *mut _pyo3::ffi::PyTypeObject,
|
_slf: *mut _pyo3::ffi::PyTypeObject,
|
||||||
_args: *mut _pyo3::ffi::PyObject,
|
_args: *mut _pyo3::ffi::PyObject,
|
||||||
_kwargs: *mut _pyo3::ffi::PyObject
|
_kwargs: *mut _pyo3::ffi::PyObject
|
||||||
) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> {
|
) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> {
|
||||||
|
@ -527,7 +531,7 @@ impl<'a> FnSpec<'a> {
|
||||||
#arg_convert
|
#arg_convert
|
||||||
let result = #call;
|
let result = #call;
|
||||||
let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(py)?;
|
let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(py)?;
|
||||||
let cell = initializer.create_cell_from_subtype(py, subtype)?;
|
let cell = initializer.create_cell_from_subtype(py, _slf)?;
|
||||||
::std::result::Result::Ok(cell as *mut _pyo3::ffi::PyObject)
|
::std::result::Result::Ok(cell as *mut _pyo3::ffi::PyObject)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -625,8 +629,8 @@ impl<'a> FnSpec<'a> {
|
||||||
// Getters / Setters / ClassAttribute are not callables on the Python side
|
// Getters / Setters / ClassAttribute are not callables on the Python side
|
||||||
FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => return None,
|
FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => return None,
|
||||||
FnType::Fn(_) => Some("self"),
|
FnType::Fn(_) => Some("self"),
|
||||||
FnType::FnModule => Some("module"),
|
FnType::FnModule(_) => Some("module"),
|
||||||
FnType::FnClass | FnType::FnNewClass => Some("cls"),
|
FnType::FnClass(_) | FnType::FnNewClass(_) => Some("cls"),
|
||||||
FnType::FnStatic | FnType::FnNew => None,
|
FnType::FnStatic | FnType::FnNew => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -191,29 +191,30 @@ pub fn impl_wrap_pyfunction(
|
||||||
|
|
||||||
let python_name = name.map_or_else(|| func.sig.ident.unraw(), |name| name.value.0);
|
let python_name = name.map_or_else(|| func.sig.ident.unraw(), |name| name.value.0);
|
||||||
|
|
||||||
let mut arguments = func
|
|
||||||
.sig
|
|
||||||
.inputs
|
|
||||||
.iter_mut()
|
|
||||||
.map(FnArg::parse)
|
|
||||||
.collect::<syn::Result<Vec<_>>>()?;
|
|
||||||
|
|
||||||
let tp = if pass_module.is_some() {
|
let tp = if pass_module.is_some() {
|
||||||
const PASS_MODULE_ERR: &str = "expected &PyModule as first argument with `pass_module`";
|
let span = match func.sig.inputs.first() {
|
||||||
ensure_spanned!(
|
Some(syn::FnArg::Typed(first_arg)) => first_arg.ty.span(),
|
||||||
!arguments.is_empty(),
|
Some(syn::FnArg::Receiver(_)) | None => bail_spanned!(
|
||||||
func.span() => PASS_MODULE_ERR
|
func.sig.paren_token.span.join() => "expected `&PyModule` or `Py<PyModule>` as first argument with `pass_module`"
|
||||||
);
|
),
|
||||||
let arg = arguments.remove(0);
|
};
|
||||||
ensure_spanned!(
|
method::FnType::FnModule(span)
|
||||||
type_is_pymodule(arg.ty),
|
|
||||||
arg.ty.span() => PASS_MODULE_ERR
|
|
||||||
);
|
|
||||||
method::FnType::FnModule
|
|
||||||
} else {
|
} else {
|
||||||
method::FnType::FnStatic
|
method::FnType::FnStatic
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let arguments = func
|
||||||
|
.sig
|
||||||
|
.inputs
|
||||||
|
.iter_mut()
|
||||||
|
.skip(if tp.skip_first_rust_argument_in_python_signature() {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
})
|
||||||
|
.map(FnArg::parse)
|
||||||
|
.collect::<syn::Result<Vec<_>>>()?;
|
||||||
|
|
||||||
let signature = if let Some(signature) = signature {
|
let signature = if let Some(signature) = signature {
|
||||||
FunctionSignature::from_arguments_and_attribute(arguments, signature)?
|
FunctionSignature::from_arguments_and_attribute(arguments, signature)?
|
||||||
} else {
|
} else {
|
||||||
|
@ -269,20 +270,3 @@ pub fn impl_wrap_pyfunction(
|
||||||
};
|
};
|
||||||
Ok(wrapped_pyfunction)
|
Ok(wrapped_pyfunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
|
||||||
if typath
|
|
||||||
.path
|
|
||||||
.segments
|
|
||||||
.last()
|
|
||||||
.map(|seg| seg.ident == "PyModule")
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
|
@ -225,7 +225,7 @@ pub fn gen_py_method(
|
||||||
&spec.get_doc(meth_attrs),
|
&spec.get_doc(meth_attrs),
|
||||||
None,
|
None,
|
||||||
)?),
|
)?),
|
||||||
(_, FnType::FnClass) => GeneratedPyMethod::Method(impl_py_method_def(
|
(_, FnType::FnClass(_)) => GeneratedPyMethod::Method(impl_py_method_def(
|
||||||
cls,
|
cls,
|
||||||
spec,
|
spec,
|
||||||
&spec.get_doc(meth_attrs),
|
&spec.get_doc(meth_attrs),
|
||||||
|
@ -238,7 +238,7 @@ pub fn gen_py_method(
|
||||||
Some(quote!(_pyo3::ffi::METH_STATIC)),
|
Some(quote!(_pyo3::ffi::METH_STATIC)),
|
||||||
)?),
|
)?),
|
||||||
// special prototypes
|
// special prototypes
|
||||||
(_, FnType::FnNew) | (_, FnType::FnNewClass) => {
|
(_, FnType::FnNew) | (_, FnType::FnNewClass(_)) => {
|
||||||
GeneratedPyMethod::Proto(impl_py_method_def_new(cls, spec)?)
|
GeneratedPyMethod::Proto(impl_py_method_def_new(cls, spec)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,7 +258,7 @@ pub fn gen_py_method(
|
||||||
doc: spec.get_doc(meth_attrs),
|
doc: spec.get_doc(meth_attrs),
|
||||||
},
|
},
|
||||||
)?),
|
)?),
|
||||||
(_, FnType::FnModule) => {
|
(_, FnType::FnModule(_)) => {
|
||||||
unreachable!("methods cannot be FnModule")
|
unreachable!("methods cannot be FnModule")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -312,7 +312,7 @@ pub fn impl_py_method_def(
|
||||||
let add_flags = flags.map(|flags| quote!(.flags(#flags)));
|
let add_flags = flags.map(|flags| quote!(.flags(#flags)));
|
||||||
let methoddef_type = match spec.tp {
|
let methoddef_type = match spec.tp {
|
||||||
FnType::FnStatic => quote!(Static),
|
FnType::FnStatic => quote!(Static),
|
||||||
FnType::FnClass => quote!(Class),
|
FnType::FnClass(_) => quote!(Class),
|
||||||
_ => quote!(Method),
|
_ => quote!(Method),
|
||||||
};
|
};
|
||||||
let methoddef = spec.get_methoddef(quote! { #cls::#wrapper_ident }, doc);
|
let methoddef = spec.get_methoddef(quote! { #cls::#wrapper_ident }, doc);
|
||||||
|
|
|
@ -176,3 +176,7 @@ pub fn apply_renaming_rule(rule: RenamingRule, name: &str) -> String {
|
||||||
RenamingRule::Uppercase => name.to_uppercase(),
|
RenamingRule::Uppercase => name.to_uppercase(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_abi3() -> bool {
|
||||||
|
pyo3_build_config::get().abi3
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pyo3-macros"
|
name = "pyo3-macros"
|
||||||
version = "0.20.0"
|
version = "0.20.3"
|
||||||
description = "Proc macros for PyO3 package"
|
description = "Proc macros for PyO3 package"
|
||||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||||
|
@ -16,10 +16,11 @@ proc-macro = true
|
||||||
[features]
|
[features]
|
||||||
multiple-pymethods = []
|
multiple-pymethods = []
|
||||||
|
|
||||||
abi3 = ["pyo3-macros-backend/abi3"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = { version = "1", default-features = false }
|
proc-macro2 = { version = "1", default-features = false }
|
||||||
quote = "1"
|
quote = "1"
|
||||||
syn = { version = "2", features = ["full", "extra-traits"] }
|
syn = { version = "2", features = ["full", "extra-traits"] }
|
||||||
pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.20.0" }
|
pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.20.3" }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
//! must not contain any other public items.
|
//! must not contain any other public items.
|
||||||
|
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
||||||
extern crate proc_macro;
|
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use pyo3_macros_backend::{
|
use pyo3_macros_backend::{
|
||||||
|
|
|
@ -21,6 +21,7 @@ classifiers = [
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,26 +1,9 @@
|
||||||
[tool.black]
|
[tool.ruff.extend-per-file-ignores]
|
||||||
target_version = ['py35']
|
"__init__.py" = ["F403"]
|
||||||
include = '\.pyi?$'
|
|
||||||
exclude = '''
|
|
||||||
|
|
||||||
(
|
|
||||||
/(
|
|
||||||
\.eggs # exclude a few common directories in the
|
|
||||||
| \.git # root of the project
|
|
||||||
| \.mypy_cache
|
|
||||||
| \.tox
|
|
||||||
| \.nox
|
|
||||||
| \.venv
|
|
||||||
| venv
|
|
||||||
| target
|
|
||||||
| dist
|
|
||||||
)/
|
|
||||||
)
|
|
||||||
'''
|
|
||||||
|
|
||||||
[tool.towncrier]
|
[tool.towncrier]
|
||||||
filename = "CHANGELOG.md"
|
filename = "CHANGELOG.md"
|
||||||
version = "0.20.0"
|
version = "0.20.3"
|
||||||
start_string = "<!-- towncrier release notes start -->\n"
|
start_string = "<!-- towncrier release notes start -->\n"
|
||||||
template = ".towncrier.template.md"
|
template = ".towncrier.template.md"
|
||||||
title_format = "## [{version}] - {project_date}"
|
title_format = "## [{version}] - {project_date}"
|
||||||
|
|
|
@ -15,3 +15,6 @@ pyo3-build-config = { path = "../pyo3-build-config" }
|
||||||
[lib]
|
[lib]
|
||||||
name = "pyo3_pytests"
|
name = "pyo3_pytests"
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
|
@ -6,19 +6,17 @@ nox.options.sessions = ["test"]
|
||||||
|
|
||||||
@nox.session
|
@nox.session
|
||||||
def test(session: nox.Session):
|
def test(session: nox.Session):
|
||||||
session.install("-rrequirements-dev.txt")
|
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||||
|
session.run_always("python", "-m", "pip", "install", "-v", ".[dev]")
|
||||||
try:
|
try:
|
||||||
session.install("--only-binary=numpy", "numpy>=1.16")
|
session.install("--only-binary=numpy", "numpy>=1.16")
|
||||||
except CommandFailed:
|
except CommandFailed:
|
||||||
# No binary wheel for numpy available on this platform
|
# No binary wheel for numpy available on this platform
|
||||||
pass
|
pass
|
||||||
session.install("maturin")
|
|
||||||
session.run_always("maturin", "develop")
|
|
||||||
session.run("pytest", *session.posargs)
|
session.run("pytest", *session.posargs)
|
||||||
|
|
||||||
|
|
||||||
@nox.session
|
@nox.session
|
||||||
def bench(session: nox.Session):
|
def bench(session: nox.Session):
|
||||||
session.install("-rrequirements-dev.txt")
|
session.install(".[dev]")
|
||||||
session.install(".")
|
|
||||||
session.run("pytest", "--benchmark-enable", "--benchmark-only", *session.posargs)
|
session.run("pytest", "--benchmark-enable", "--benchmark-only", *session.posargs)
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
from .pyo3_pytests import *
|
|
|
@ -17,3 +17,12 @@ classifiers = [
|
||||||
"Operating System :: POSIX",
|
"Operating System :: POSIX",
|
||||||
"Operating System :: MacOS :: MacOS X",
|
"Operating System :: MacOS :: MacOS X",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"hypothesis>=3.55",
|
||||||
|
"pytest-asyncio>=0.21",
|
||||||
|
"pytest-benchmark>=3.4",
|
||||||
|
"pytest>=6.0",
|
||||||
|
"typing_extensions>=4.0.0"
|
||||||
|
]
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
hypothesis>=3.55
|
|
||||||
pytest>=6.0
|
|
||||||
pytest-asyncio>=0.21
|
|
||||||
pytest-benchmark>=3.4
|
|
||||||
psutil>=5.6
|
|
||||||
typing_extensions>=4.0.0
|
|
|
@ -1,5 +1,4 @@
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::{types::PyModule, Python};
|
|
||||||
|
|
||||||
#[pyclass]
|
#[pyclass]
|
||||||
struct Eq(i64);
|
struct Eq(i64);
|
||||||
|
|
|
@ -118,16 +118,15 @@ def test_time(args, kwargs):
|
||||||
|
|
||||||
|
|
||||||
@given(t=st.times())
|
@given(t=st.times())
|
||||||
def test_time(t):
|
def test_time_hypothesis(t):
|
||||||
act = rdt.get_time_tuple(t)
|
act = rdt.get_time_tuple(t)
|
||||||
exp = (t.hour, t.minute, t.second, t.microsecond)
|
exp = (t.hour, t.minute, t.second, t.microsecond)
|
||||||
|
|
||||||
assert act == exp
|
assert act == exp
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(PYPY, reason="Feature not available on PyPy")
|
|
||||||
@given(t=st.times())
|
@given(t=st.times())
|
||||||
def test_time_fold(t):
|
def test_time_tuple_fold(t):
|
||||||
t_nofold = t.replace(fold=0)
|
t_nofold = t.replace(fold=0)
|
||||||
t_fold = t.replace(fold=1)
|
t_fold = t.replace(fold=1)
|
||||||
|
|
||||||
|
@ -138,9 +137,8 @@ def test_time_fold(t):
|
||||||
assert act == exp
|
assert act == exp
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(PYPY, reason="Feature not available on PyPy")
|
|
||||||
@pytest.mark.parametrize("fold", [False, True])
|
@pytest.mark.parametrize("fold", [False, True])
|
||||||
def test_time_fold(fold):
|
def test_time_with_fold(fold):
|
||||||
t = rdt.time_with_fold(0, 0, 0, 0, None, fold)
|
t = rdt.time_with_fold(0, 0, 0, 0, None, fold)
|
||||||
assert t.fold == fold
|
assert t.fold == fold
|
||||||
|
|
||||||
|
@ -206,7 +204,6 @@ def test_datetime_tuple(dt):
|
||||||
assert act == exp
|
assert act == exp
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(PYPY, reason="Feature not available on PyPy")
|
|
||||||
@given(dt=st.datetimes())
|
@given(dt=st.datetimes())
|
||||||
def test_datetime_tuple_fold(dt):
|
def test_datetime_tuple_fold(dt):
|
||||||
dt_fold = dt.replace(fold=1)
|
dt_fold = dt.replace(fold=1)
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import gc
|
import gc
|
||||||
import platform
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pytest
|
|
||||||
from pyo3_pytests.objstore import ObjStore
|
from pyo3_pytests.objstore import ObjStore
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import platform
|
|
||||||
|
|
||||||
from pyo3_pytests.subclassing import Subclassable
|
from pyo3_pytests.subclassing import Subclassable
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
[toolchain]
|
|
||||||
components = [ "rust-src" ]
|
|
|
@ -51,7 +51,6 @@ use chrono::{
|
||||||
DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Offset, TimeZone, Timelike,
|
DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Offset, TimeZone, Timelike,
|
||||||
};
|
};
|
||||||
use pyo3_ffi::{PyDateTime_IMPORT, PyTimeZone_FromOffset};
|
use pyo3_ffi::{PyDateTime_IMPORT, PyTimeZone_FromOffset};
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
impl ToPyObject for Duration {
|
impl ToPyObject for Duration {
|
||||||
fn to_object(&self, py: Python<'_>) -> PyObject {
|
fn to_object(&self, py: Python<'_>) -> PyObject {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue