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:
|
||||
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 }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
@ -65,7 +65,7 @@ jobs:
|
|||
run: echo "TRYBUILD=overwrite" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build docs
|
||||
run: cargo doc --no-deps --no-default-features --features "full ${{ inputs.extra-features }}"
|
||||
run: nox -s docs
|
||||
|
||||
- name: Build (no features)
|
||||
run: cargo build --lib --tests --no-default-features
|
||||
|
|
|
@ -27,10 +27,19 @@ jobs:
|
|||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
- name: Check python formatting (black)
|
||||
run: nox -s fmt-py
|
||||
- name: Check python formatting and lints (ruff)
|
||||
run: nox -s ruff
|
||||
- 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:
|
||||
needs: [fmt]
|
||||
|
@ -104,7 +113,17 @@ jobs:
|
|||
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 }}
|
||||
continue-on-error: ${{ matrix.platform.rust != 'stable' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
|
@ -144,7 +163,7 @@ jobs:
|
|||
matrix:
|
||||
extra-features: ["multiple-pymethods"]
|
||||
rust: [stable]
|
||||
python-version: ["3.11"]
|
||||
python-version: ["3.12"]
|
||||
platform:
|
||||
[
|
||||
{
|
||||
|
@ -221,7 +240,7 @@ jobs:
|
|||
include:
|
||||
# Test minimal supported Rust version
|
||||
- rust: 1.56.0
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
platform:
|
||||
{
|
||||
os: "ubuntu-latest",
|
||||
|
@ -233,7 +252,7 @@ jobs:
|
|||
|
||||
# Test the `nightly` feature
|
||||
- rust: nightly
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
platform:
|
||||
{
|
||||
os: "ubuntu-latest",
|
||||
|
@ -244,7 +263,7 @@ jobs:
|
|||
|
||||
# Test 32-bit Windows only with the latest Python version
|
||||
- rust: stable
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
platform:
|
||||
{
|
||||
os: "windows-latest",
|
||||
|
@ -287,13 +306,29 @@ jobs:
|
|||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
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: nox -s test-rust -- careful skip-full
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
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:
|
||||
needs: [fmt]
|
||||
name: coverage-${{ matrix.os }}
|
||||
|
@ -318,7 +353,7 @@ jobs:
|
|||
- uses: dtolnay/rust-toolchain@stable
|
||||
if: steps.should-skip.outputs.skip != 'true'
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
components: llvm-tools-preview,rust-src
|
||||
- name: Install cargo-llvm-cov
|
||||
if: steps.should-skip.outputs.skip != 'true'
|
||||
uses: taiki-e/install-action@cargo-llvm-cov
|
||||
|
@ -334,17 +369,21 @@ jobs:
|
|||
|
||||
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
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
# TODO bump emscripten builds to test on 3.12
|
||||
python-version: 3.11
|
||||
id: setup-python
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-unknown-emscripten
|
||||
- uses: actions/setup-node@v3
|
||||
components: rust-src
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 14
|
||||
- run: python -m pip install --upgrade pip && pip install nox
|
||||
|
@ -364,6 +403,8 @@ jobs:
|
|||
run: nox -s test-emscripten
|
||||
|
||||
test-debug:
|
||||
needs: [fmt]
|
||||
if: github.ref != 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
@ -406,6 +447,34 @@ jobs:
|
|||
echo PYO3_CONFIG_FILE=$PYO3_CONFIG_FILE >> $GITHUB_ENV
|
||||
- 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:
|
||||
needs:
|
||||
- fmt
|
||||
|
@ -415,8 +484,12 @@ jobs:
|
|||
- build-full
|
||||
- valgrind
|
||||
- careful
|
||||
- docsrs
|
||||
- coverage
|
||||
- emscripten
|
||||
- test-debug
|
||||
- test-version-limits
|
||||
- check-feature-powerset
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
|
|
@ -23,3 +23,4 @@ valgrind-python.supp
|
|||
*.pyd
|
||||
lcov.info
|
||||
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 -->
|
||||
|
||||
## [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
|
||||
|
||||
### Packaging
|
||||
|
@ -1599,7 +1640,10 @@ Yanked
|
|||
|
||||
- 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.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
|
||||
|
|
72
Cargo.toml
72
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3"
|
||||
version = "0.20.0"
|
||||
version = "0.20.3"
|
||||
description = "Bindings to Python interpreter"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
readme = "README.md"
|
||||
|
@ -19,12 +19,13 @@ cfg-if = "1.0"
|
|||
libc = "0.2.62"
|
||||
parking_lot = ">= 0.11, < 0.13"
|
||||
memoffset = "0.9"
|
||||
portable-atomic = "1.0"
|
||||
|
||||
# ffi bindings to the python interpreter, split into a separate crate so they can be used independently
|
||||
pyo3-ffi = { path = "pyo3-ffi", version = "=0.20.0" }
|
||||
pyo3-ffi = { path = "pyo3-ffi", version = "=0.20.3" }
|
||||
|
||||
# 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 }
|
||||
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
|
||||
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 }
|
||||
hashbrown = { version = ">= 0.9, < 0.15", 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 }
|
||||
rust_decimal = { version = "1.0.0", default-features = false, optional = true }
|
||||
serde = { version = "1.0", optional = true }
|
||||
smallvec = { version = "1.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_approx_eq = "1.1.0"
|
||||
|
@ -51,12 +54,11 @@ proptest = { version = "1.0", default-features = false, features = ["std"] }
|
|||
send_wrapper = "0.6"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.61"
|
||||
rayon = "1.0.2"
|
||||
rust_decimal = { version = "1.8.0", features = ["std"] }
|
||||
rayon = "1.6.1"
|
||||
widestring = "0.5.1"
|
||||
|
||||
[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]
|
||||
default = ["macros"]
|
||||
|
@ -77,14 +79,15 @@ multiple-pymethods = ["inventory", "pyo3-macros/multiple-pymethods"]
|
|||
extension-module = ["pyo3-ffi/extension-module"]
|
||||
|
||||
# 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.
|
||||
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-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-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.
|
||||
generate-import-lib = ["pyo3-ffi/generate-import-lib"]
|
||||
|
@ -95,21 +98,26 @@ auto-initialize = []
|
|||
# Optimizes PyObject to Vec conversion and so on.
|
||||
nightly = []
|
||||
|
||||
# Disables the checks for use in subinterpreters.
|
||||
unsafe-allow-subinterpreters = []
|
||||
|
||||
# Activates all additional features
|
||||
# This is mostly intended for testing purposes - activating *all* of these isn't particularly useful.
|
||||
full = [
|
||||
"macros",
|
||||
# "multiple-pymethods", # TODO re-add this when MSRV is greater than 1.62
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"either",
|
||||
"experimental-inspect",
|
||||
"eyre",
|
||||
"hashbrown",
|
||||
"indexmap",
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
"indexmap",
|
||||
"eyre",
|
||||
"anyhow",
|
||||
"experimental-inspect",
|
||||
"rust_decimal",
|
||||
"serde",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[workspace]
|
||||
|
@ -124,5 +132,37 @@ members = [
|
|||
|
||||
[package.metadata.docs.rs]
|
||||
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"]
|
||||
|
||||
[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"]
|
||||
|
||||
[dependencies]
|
||||
pyo3 = { version = "0.20.0", features = ["extension-module"] }
|
||||
pyo3 = { version = "0.20.3", features = ["extension-module"] }
|
||||
```
|
||||
|
||||
**`src/lib.rs`**
|
||||
|
@ -137,7 +137,7 @@ Start a new project with `cargo new` and add `pyo3` to the `Cargo.toml` like th
|
|||
|
||||
```toml
|
||||
[dependencies.pyo3]
|
||||
version = "0.20.0"
|
||||
version = "0.20.3"
|
||||
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<()> {
|
||||
let interpreter_config = pyo3_build_config::get();
|
||||
|
||||
interpreter_config.emit_pyo3_cfgs();
|
||||
|
||||
ensure_auto_initialize_ok(interpreter_config)?;
|
||||
|
||||
for cfg in interpreter_config.build_script_outputs() {
|
||||
println!("{}", cfg)
|
||||
}
|
||||
|
||||
// Emit cfgs like `thread_local_const_init`
|
||||
print_feature_cfgs();
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
pybuilddir.txt
|
|
@ -5,7 +5,7 @@ publish = false
|
|||
edition = "2021"
|
||||
|
||||
[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]]
|
||||
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. |
|
||||
| `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 |
|
||||
| `sequential` | Illustrates how to use pyo3-ffi to write subinterpreter-safe modules |
|
||||
|
||||
## 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/pyproject.toml", "pyproject.toml");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -5,3 +5,6 @@ build-backend = "maturin"
|
|||
[project]
|
||||
name = "{{project-name}}"
|
||||
version = "0.1.0"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
|
|
@ -2,8 +2,7 @@ import nox
|
|||
|
||||
|
||||
@nox.session
|
||||
def python(session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install("maturin")
|
||||
session.run_always("maturin", "develop")
|
||||
def python(session: nox.Session):
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest")
|
||||
|
|
|
@ -14,3 +14,6 @@ classifiers = [
|
|||
"Operating System :: POSIX",
|
||||
"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
|
||||
def say_hello():
|
||||
print("hello")
|
||||
|
|
|
@ -45,7 +45,7 @@ def test_discussion_2598():
|
|||
@Counter
|
||||
def say_hello():
|
||||
if say_hello.count < 2:
|
||||
print(f"hello from decorator")
|
||||
print("hello from decorator")
|
||||
|
||||
say_hello()
|
||||
say_hello()
|
||||
|
|
|
@ -5,3 +5,6 @@ build-backend = "maturin"
|
|||
[project]
|
||||
name = "{{project-name}}"
|
||||
version = "0.1.0"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
|
|
@ -2,8 +2,7 @@ import nox
|
|||
|
||||
|
||||
@nox.session
|
||||
def python(session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install("maturin")
|
||||
session.run_always("maturin", "develop")
|
||||
def python(session: nox.Session):
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest")
|
||||
|
|
|
@ -14,3 +14,6 @@ classifiers = [
|
|||
"Operating System :: POSIX",
|
||||
"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/pyproject.toml", "pyproject.toml");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -5,3 +5,6 @@ build-backend = "maturin"
|
|||
[project]
|
||||
name = "{{project-name}}"
|
||||
version = "0.1.0"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# 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 __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"]
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import nox
|
|||
|
||||
@nox.session
|
||||
def python(session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install("maturin")
|
||||
session.run_always("maturin", "develop")
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest")
|
||||
|
|
|
@ -14,3 +14,6 @@ classifiers = [
|
|||
"Operating System :: POSIX",
|
||||
"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/plugin_api/Cargo.toml", "plugin_api/Cargo.toml");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -3,7 +3,6 @@ import nox
|
|||
|
||||
@nox.session
|
||||
def python(session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install("maturin")
|
||||
session.run_always("maturin", "develop", "--features", "extension-module")
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest")
|
||||
|
|
|
@ -10,3 +10,6 @@ classifiers = [
|
|||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"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():
|
||||
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/setup.cfg", "setup.cfg");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -2,7 +2,7 @@ import nox
|
|||
|
||||
|
||||
@nox.session
|
||||
def python(session):
|
||||
def python(session: nox.Session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.run_always(
|
||||
"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
|
||||
# optional: include the documentation from the Rust module
|
||||
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"]
|
||||
|
||||
|
|
|
@ -5,3 +5,6 @@ build-backend = "maturin"
|
|||
[project]
|
||||
name = "{{project-name}}"
|
||||
version = "0.1.0"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
|
|
@ -2,8 +2,7 @@ import nox
|
|||
|
||||
|
||||
@nox.session
|
||||
def python(session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install("maturin")
|
||||
session.run_always("maturin", "develop")
|
||||
def python(session: nox.Session):
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest")
|
||||
|
|
|
@ -3,7 +3,7 @@ requires = ["maturin>=1,<2"]
|
|||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "string sum"
|
||||
name = "string_sum"
|
||||
version = "0.1.0"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
|
@ -14,3 +14,6 @@ classifiers = [
|
|||
"Operating System :: POSIX",
|
||||
"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(
|
||||
TypeError, match="sum_as_string expected an int for positional argument 1"
|
||||
) as e:
|
||||
):
|
||||
sum_as_string(a, b)
|
||||
|
||||
|
||||
|
@ -23,19 +23,19 @@ def test_err2():
|
|||
|
||||
with pytest.raises(
|
||||
TypeError, match="sum_as_string expected an int for positional argument 2"
|
||||
) as e:
|
||||
):
|
||||
sum_as_string(a, b)
|
||||
|
||||
|
||||
def test_overflow1():
|
||||
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)
|
||||
|
||||
|
||||
def test_overflow2():
|
||||
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)
|
||||
|
|
|
@ -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/pyproject.toml", "pyproject.toml");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=41.0.0", "wheel", "setuptools_rust>=1.0.0"]
|
||||
requires = ["maturin>=1,<2"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "{{project-name}}"
|
||||
version = "0.1.0"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
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
|
||||
def test(session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install("maturin")
|
||||
session.run_always("maturin", "develop")
|
||||
def test(session: nox.Session):
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest")
|
||||
|
||||
|
||||
@nox.session
|
||||
def bench(session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install(".")
|
||||
def bench(session: nox.Session):
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest", "--benchmark-enable")
|
||||
|
|
|
@ -15,6 +15,8 @@ classifiers = [
|
|||
"Operating System :: MacOS :: MacOS X",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
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 B: PyO3 and rust-cpython](rust_cpython.md)
|
||||
[Appendix C: Trait bounds](trait_bounds.md)
|
||||
[Appendix D: Python typing hints](python_typing_hints.md)
|
||||
[Appendix B: Trait bounds](trait_bounds.md)
|
||||
[Appendix C: Python typing hints](python_typing_hints.md)
|
||||
[CHANGELOG](changelog.md)
|
||||
|
||||
---
|
||||
|
|
|
@ -337,10 +337,27 @@ impl SubSubClass {
|
|||
let super_ = self_.into_super(); // Get PyRef<'_, SubClass>
|
||||
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| {
|
||||
# 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) |
|
||||
| ------------- |:-------------------------------:|:--------------------:|
|
||||
| `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` |
|
||||
| `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` |
|
||||
|
|
|
@ -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)
|
||||
- [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`
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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 os
|
||||
import re
|
||||
|
@ -7,11 +8,20 @@ import tempfile
|
|||
from functools import lru_cache
|
||||
from glob import glob
|
||||
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.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
|
||||
|
@ -66,22 +76,17 @@ def coverage(session: nox.Session) -> None:
|
|||
)
|
||||
|
||||
|
||||
@nox.session
|
||||
def fmt(session: nox.Session):
|
||||
fmt_rust(session)
|
||||
fmt_py(session)
|
||||
|
||||
|
||||
@nox.session(name="fmt-rust", venv_backend="none")
|
||||
def fmt_rust(session: nox.Session):
|
||||
@nox.session(venv_backend="none")
|
||||
def rustfmt(session: nox.Session):
|
||||
_run_cargo(session, "fmt", "--all", "--check")
|
||||
_run_cargo(session, "fmt", _FFI_CHECK, "--all", "--check")
|
||||
|
||||
|
||||
@nox.session(name="fmt-py")
|
||||
def fmt_py(session: nox.Session):
|
||||
session.install("black==22.3.0")
|
||||
_run(session, "black", ".", "--check")
|
||||
@nox.session(name="ruff")
|
||||
def ruff(session: nox.Session):
|
||||
session.install("ruff")
|
||||
_run(session, "ruff", "format", ".", "--check")
|
||||
_run(session, "ruff", "check", ".")
|
||||
|
||||
|
||||
@nox.session(name="clippy", venv_backend="none")
|
||||
|
@ -105,7 +110,7 @@ def _clippy(session: nox.Session, *, env: Dict[str, str] = None) -> bool:
|
|||
"--deny=warnings",
|
||||
env=env,
|
||||
)
|
||||
except Exception:
|
||||
except nox.command.CommandFailed:
|
||||
success = False
|
||||
return success
|
||||
|
||||
|
@ -221,7 +226,7 @@ def contributors(session: nox.Session) -> None:
|
|||
for commit in body["commits"]:
|
||||
try:
|
||||
authors.add(commit["author"]["login"])
|
||||
except:
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if "next" in resp.links:
|
||||
|
@ -261,6 +266,7 @@ def build_emscripten(session: nox.Session):
|
|||
"make",
|
||||
"-C",
|
||||
str(info.emscripten_dir),
|
||||
f"PYTHON={sys.executable}",
|
||||
f"BUILDROOT={info.builddir}",
|
||||
f"PYMAJORMINORMICRO={info.pymajorminormicro}",
|
||||
f"PYPRERELEASE={info.pydev}",
|
||||
|
@ -469,10 +475,8 @@ def check_changelog(session: nox.Session):
|
|||
def set_minimal_package_versions(session: nox.Session):
|
||||
from collections import defaultdict
|
||||
|
||||
try:
|
||||
import tomllib as toml
|
||||
except ImportError:
|
||||
import toml
|
||||
if toml is None:
|
||||
session.error("requires Python 3.11 or `toml` to be installed")
|
||||
|
||||
projects = (
|
||||
None,
|
||||
|
@ -484,8 +488,8 @@ def set_minimal_package_versions(session: nox.Session):
|
|||
min_pkg_versions = {
|
||||
"rust_decimal": "1.26.1",
|
||||
"csv": "1.1.6",
|
||||
"indexmap": "1.9.3",
|
||||
"hashbrown": "0.12.3",
|
||||
"indexmap": "1.6.2",
|
||||
"hashbrown": "0.9.1",
|
||||
"log": "0.4.17",
|
||||
"once_cell": "1.17.2",
|
||||
"rayon": "1.6.1",
|
||||
|
@ -494,6 +498,10 @@ def set_minimal_package_versions(session: nox.Session):
|
|||
"proptest": "1.0.0",
|
||||
"chrono": "0.4.25",
|
||||
"byteorder": "1.4.3",
|
||||
"crossbeam-channel": "0.5.8",
|
||||
"crossbeam-deque": "0.8.3",
|
||||
"crossbeam-epoch": "0.9.15",
|
||||
"crossbeam-utils": "0.8.16",
|
||||
}
|
||||
|
||||
# run cargo update first to ensure that everything is at highest
|
||||
|
@ -552,6 +560,101 @@ def ffi_check(session: nox.Session):
|
|||
_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:
|
||||
# pyo3-ffi-check needs to scrape docs of pyo3-ffi
|
||||
_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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
@ -688,24 +797,14 @@ def _get_output(*args: str) -> str:
|
|||
def _for_all_version_configs(
|
||||
session: nox.Session, job: Callable[[Dict[str, str]], None]
|
||||
) -> None:
|
||||
with tempfile.NamedTemporaryFile("r+") as config:
|
||||
env = os.environ.copy()
|
||||
env["PYO3_CONFIG_FILE"] = config.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()
|
||||
with _config_file() as config_file:
|
||||
env["PYO3_CONFIG_FILE"] = config_file.name
|
||||
|
||||
def _job_with_config(implementation, version):
|
||||
session.log(f"{implementation} {version}")
|
||||
return job(env)
|
||||
config_file.set(implementation, version)
|
||||
job(env)
|
||||
|
||||
for version in PY_VERSIONS:
|
||||
_job_with_config("CPython", version)
|
||||
|
@ -714,5 +813,34 @@ suppress_build_script_link_lines=true
|
|||
_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"
|
||||
_FFI_CHECK = "--manifest-path=pyo3-ffi-check/Cargo.toml"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.20.0"
|
||||
version = "0.20.3"
|
||||
description = "Build configuration for the PyO3 ecosystem"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||
|
@ -35,7 +35,8 @@ abi3-py37 = ["abi3-py38"]
|
|||
abi3-py38 = ["abi3-py39"]
|
||||
abi3-py39 = ["abi3-py310"]
|
||||
abi3-py310 = ["abi3-py311"]
|
||||
abi3-py311 = ["abi3"]
|
||||
abi3-py311 = ["abi3-py312"]
|
||||
abi3-py312 = ["abi3"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["resolve-config"]
|
||||
|
|
|
@ -12,15 +12,21 @@ macro_rules! ensure {
|
|||
($condition:expr, $($args: tt)+) => { if !($condition) { bail!($($args)+) } };
|
||||
}
|
||||
|
||||
/// Show warning. If needed, please extend this macro to support arguments.
|
||||
/// Show warning.
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! warn {
|
||||
($msg: literal) => {
|
||||
println!(concat!("cargo:warning=", $msg))
|
||||
($($args: tt)+) => {
|
||||
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::{
|
||||
collections::{HashMap, HashSet},
|
||||
convert::AsRef,
|
||||
env,
|
||||
ffi::{OsStr, OsString},
|
||||
fmt::Display,
|
||||
|
@ -27,14 +26,14 @@ use target_lexicon::{Environment, OperatingSystem};
|
|||
use crate::{
|
||||
bail, ensure,
|
||||
errors::{Context, Error, Result},
|
||||
warn,
|
||||
format_warn, warn,
|
||||
};
|
||||
|
||||
/// Minimum Python version PyO3 supports.
|
||||
const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 7 };
|
||||
|
||||
/// Maximum Python version that can be used as minimum required Python version with abi3.
|
||||
const ABI3_MAX_MINOR: u8 = 11;
|
||||
const ABI3_MAX_MINOR: u8 = 12;
|
||||
|
||||
/// Gets an environment variable owned by cargo.
|
||||
///
|
||||
|
@ -155,30 +154,41 @@ pub struct InterpreterConfig {
|
|||
impl InterpreterConfig {
|
||||
#[doc(hidden)]
|
||||
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.
|
||||
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
|
||||
// Py_3_6 (to avoid silently breaking users who depend on this cfg).
|
||||
for i in 6..=self.version.minor {
|
||||
println!("cargo:rustc-cfg=Py_3_{}", i);
|
||||
out.push(format!("cargo:rustc-cfg=Py_3_{}", i));
|
||||
}
|
||||
|
||||
if self.implementation.is_pypy() {
|
||||
println!("cargo:rustc-cfg=PyPy");
|
||||
out.push("cargo:rustc-cfg=PyPy".to_owned());
|
||||
if self.abi3 {
|
||||
warn!(
|
||||
out.push(format_warn!(
|
||||
"PyPy does not yet support abi3 so the build artifacts will be version-specific. \
|
||||
See https://foss.heptapod.net/pypy/pypy/-/issues/3397 for more information."
|
||||
);
|
||||
));
|
||||
}
|
||||
} else if self.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 {
|
||||
println!("cargo:rustc-cfg=py_sys_config=\"{}\"", flag);
|
||||
out.push(format!("cargo:rustc-cfg=py_sys_config=\"{}\"", flag));
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
|
@ -697,6 +707,7 @@ fn have_python_interpreter() -> bool {
|
|||
/// Must be called from a PyO3 crate build script.
|
||||
fn is_abi3() -> bool {
|
||||
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.
|
||||
|
@ -1011,12 +1022,12 @@ impl BuildFlags {
|
|||
Self(
|
||||
BuildFlags::ALL
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|flag| {
|
||||
config_map
|
||||
.get_value(&flag.to_string())
|
||||
.map_or(false, |value| value == "1")
|
||||
})
|
||||
.cloned()
|
||||
.collect(),
|
||||
)
|
||||
.fixup()
|
||||
|
@ -1357,9 +1368,13 @@ fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result<In
|
|||
let version = cross_compile_config
|
||||
.version
|
||||
.or_else(get_abi3_version)
|
||||
.ok_or(
|
||||
.ok_or_else(||
|
||||
format!(
|
||||
"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();
|
||||
|
@ -1776,7 +1791,6 @@ fn unescape(escaped: &str) -> Vec<u8> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::iter::FromIterator;
|
||||
use target_lexicon::triple;
|
||||
|
||||
use super::*;
|
||||
|
@ -2581,4 +2595,114 @@ mod tests {
|
|||
.expect("failed to run Python script");
|
||||
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).
|
||||
#[cfg(feature = "resolve-config")]
|
||||
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.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.20.0"
|
||||
version = "0.20.3"
|
||||
description = "Python-API bindings for the PyO3 ecosystem"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
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-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39"]
|
||||
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.
|
||||
generate-import-lib = ["pyo3-build-config/python3-dll-a"]
|
||||
|
||||
|
||||
[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,
|
||||
InterpreterConfig, PythonVersion,
|
||||
},
|
||||
PythonImplementation,
|
||||
};
|
||||
|
||||
/// 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<()> {
|
||||
// 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!(
|
||||
interpreter_config.version >= MINIMUM_SUPPORTED_VERSION,
|
||||
interpreter_config.version >= versions.min,
|
||||
"the configured Python interpreter version ({}) is lower than PyO3's minimum supported 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(())
|
||||
}
|
||||
|
@ -93,7 +151,9 @@ fn configure_pyo3() -> Result<()> {
|
|||
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.
|
||||
for line in &interpreter_config.extra_build_script_lines {
|
||||
|
@ -109,7 +169,7 @@ fn configure_pyo3() -> Result<()> {
|
|||
fn print_config_and_exit(config: &InterpreterConfig) {
|
||||
println!("\n-- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile --");
|
||||
config
|
||||
.to_writer(&mut std::io::stdout())
|
||||
.to_writer(std::io::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");
|
||||
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);
|
||||
|
|
|
@ -65,4 +65,10 @@ pub struct _frozen {
|
|||
extern "C" {
|
||||
#[cfg(not(PyPy))]
|
||||
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;
|
||||
// skipped sysmodule.h
|
||||
pub(crate) mod floatobject;
|
||||
#[cfg(not(PyPy))]
|
||||
pub(crate) mod pyframe;
|
||||
pub(crate) mod tupleobject;
|
||||
pub(crate) mod unicodeobject;
|
||||
|
@ -60,7 +59,7 @@ pub use self::object::*;
|
|||
pub use self::objimpl::*;
|
||||
pub use self::pydebug::*;
|
||||
pub use self::pyerrors::*;
|
||||
#[cfg(not(PyPy))]
|
||||
#[cfg(Py_3_11)]
|
||||
pub use self::pyframe::*;
|
||||
#[cfg(all(Py_3_8, not(PyPy)))]
|
||||
pub use self::pylifecycle::*;
|
||||
|
@ -69,4 +68,5 @@ pub use self::pystate::*;
|
|||
pub use self::pythonrun::*;
|
||||
pub use self::tupleobject::*;
|
||||
pub use self::unicodeobject::*;
|
||||
#[cfg(not(PyPy))]
|
||||
pub use self::weakrefobject::*;
|
||||
|
|
|
@ -262,6 +262,7 @@ pub use self::boolobject::*;
|
|||
pub use self::bytearrayobject::*;
|
||||
pub use self::bytesobject::*;
|
||||
pub use self::ceval::*;
|
||||
#[cfg(Py_LIMITED_API)]
|
||||
pub use self::code::*;
|
||||
pub use self::codecs::*;
|
||||
pub use self::compile::*;
|
||||
|
@ -326,6 +327,7 @@ mod bytesobject;
|
|||
// skipped cellobject.h
|
||||
mod ceval;
|
||||
// skipped classobject.h
|
||||
#[cfg(Py_LIMITED_API)]
|
||||
mod code;
|
||||
mod codecs;
|
||||
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)))]
|
||||
|
@ -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)))]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.20.0"
|
||||
version = "0.20.3"
|
||||
description = "Code generation for PyO3 package"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||
|
@ -14,14 +14,15 @@ edition = "2021"
|
|||
# not to depend on proc-macro itself.
|
||||
# See https://github.com/PyO3/pyo3/pull/810 for more.
|
||||
[dependencies]
|
||||
quote = { version = "1", default-features = false }
|
||||
proc-macro2 = { version = "1", default-features = false }
|
||||
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]
|
||||
version = "2"
|
||||
default-features = false
|
||||
features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"]
|
||||
|
||||
[features]
|
||||
abi3 = []
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::params::impl_arg_params;
|
|||
use crate::pyfunction::{FunctionSignature, PyFunctionArgPyO3Attributes};
|
||||
use crate::pyfunction::{PyFunctionOptions, SignatureAttribute};
|
||||
use crate::quotes;
|
||||
use crate::utils::{self, PythonDoc};
|
||||
use crate::utils::{self, is_abi3, PythonDoc};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::ToTokens;
|
||||
use quote::{quote, quote_spanned};
|
||||
|
@ -78,10 +78,10 @@ pub enum FnType {
|
|||
Setter(SelfType),
|
||||
Fn(SelfType),
|
||||
FnNew,
|
||||
FnNewClass,
|
||||
FnClass,
|
||||
FnNewClass(Span),
|
||||
FnClass(Span),
|
||||
FnStatic,
|
||||
FnModule,
|
||||
FnModule(Span),
|
||||
ClassAttribute,
|
||||
}
|
||||
|
||||
|
@ -91,9 +91,9 @@ impl FnType {
|
|||
FnType::Getter(_)
|
||||
| FnType::Setter(_)
|
||||
| FnType::Fn(_)
|
||||
| FnType::FnClass
|
||||
| FnType::FnNewClass
|
||||
| FnType::FnModule => true,
|
||||
| FnType::FnClass(_)
|
||||
| FnType::FnNewClass(_)
|
||||
| FnType::FnModule(_) => true,
|
||||
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => false,
|
||||
}
|
||||
}
|
||||
|
@ -111,14 +111,18 @@ impl FnType {
|
|||
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => {
|
||||
quote!()
|
||||
}
|
||||
FnType::FnClass | FnType::FnNewClass => {
|
||||
quote! {
|
||||
_pyo3::types::PyType::from_type_ptr(py, _slf as *mut _pyo3::ffi::PyTypeObject),
|
||||
FnType::FnClass(span) | FnType::FnNewClass(span) => {
|
||||
let py = syn::Ident::new("py", Span::call_site());
|
||||
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 => {
|
||||
quote! {
|
||||
py.from_borrowed_ptr::<_pyo3::types::PyModule>(_slf),
|
||||
FnType::FnModule(span) => {
|
||||
quote_spanned! { *span =>
|
||||
#[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)
|
||||
.and_then(
|
||||
#[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)
|
||||
)
|
||||
|
||||
|
@ -208,8 +213,8 @@ impl CallingConvention {
|
|||
} else if signature.python_signature.kwargs.is_some() {
|
||||
// for functions that accept **kwargs, always prefer varargs
|
||||
Self::Varargs
|
||||
} else if cfg!(not(feature = "abi3")) {
|
||||
// Not available in the Stable ABI as of Python 3.10
|
||||
} else if !is_abi3() {
|
||||
// FIXME: available in the stable ABI since 3.10
|
||||
Self::Fastcall
|
||||
} else {
|
||||
Self::Varargs
|
||||
|
@ -303,7 +308,7 @@ impl<'a> FnSpec<'a> {
|
|||
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
|
||||
} else {
|
||||
CallingConvention::from_signature(&signature)
|
||||
|
@ -351,36 +356,40 @@ impl<'a> FnSpec<'a> {
|
|||
.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() {
|
||||
[] => FnType::Fn(parse_receiver(
|
||||
"static method needs #[staticmethod] attribute",
|
||||
)?),
|
||||
[MethodTypeAttribute::StaticMethod(_)] => FnType::FnStatic,
|
||||
[MethodTypeAttribute::ClassAttribute(_)] => FnType::ClassAttribute,
|
||||
[MethodTypeAttribute::New(_)]
|
||||
| [MethodTypeAttribute::New(_), MethodTypeAttribute::ClassMethod(_)]
|
||||
| [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(_)]) {
|
||||
[MethodTypeAttribute::New(_)] => {
|
||||
set_name_to_new()?;
|
||||
FnType::FnNew
|
||||
} else {
|
||||
FnType::FnNewClass
|
||||
}
|
||||
[MethodTypeAttribute::New(_), MethodTypeAttribute::ClassMethod(span)]
|
||||
| [MethodTypeAttribute::ClassMethod(span), MethodTypeAttribute::New(_)] => {
|
||||
set_name_to_new()?;
|
||||
FnType::FnNewClass(*span)
|
||||
}
|
||||
[MethodTypeAttribute::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
|
||||
// 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!(
|
||||
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)] => {
|
||||
if let Some(name) = name.take() {
|
||||
|
@ -508,17 +517,12 @@ impl<'a> FnSpec<'a> {
|
|||
}
|
||||
CallingConvention::TpNew => {
|
||||
let (arg_convert, args) = impl_arg_params(self, cls, false)?;
|
||||
let call = match &self.tp {
|
||||
FnType::FnNew => quote! { #rust_name(#(#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),
|
||||
};
|
||||
let self_arg = self.tp.self_arg(cls, ExtractErrorMode::Raise);
|
||||
let call = quote! { #rust_name(#self_arg #(#args),*) };
|
||||
quote! {
|
||||
unsafe fn #ident(
|
||||
py: _pyo3::Python<'_>,
|
||||
subtype: *mut _pyo3::ffi::PyTypeObject,
|
||||
_slf: *mut _pyo3::ffi::PyTypeObject,
|
||||
_args: *mut _pyo3::ffi::PyObject,
|
||||
_kwargs: *mut _pyo3::ffi::PyObject
|
||||
) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> {
|
||||
|
@ -527,7 +531,7 @@ impl<'a> FnSpec<'a> {
|
|||
#arg_convert
|
||||
let result = #call;
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -625,8 +629,8 @@ impl<'a> FnSpec<'a> {
|
|||
// Getters / Setters / ClassAttribute are not callables on the Python side
|
||||
FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => return None,
|
||||
FnType::Fn(_) => Some("self"),
|
||||
FnType::FnModule => Some("module"),
|
||||
FnType::FnClass | FnType::FnNewClass => Some("cls"),
|
||||
FnType::FnModule(_) => Some("module"),
|
||||
FnType::FnClass(_) | FnType::FnNewClass(_) => Some("cls"),
|
||||
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 mut arguments = func
|
||||
.sig
|
||||
.inputs
|
||||
.iter_mut()
|
||||
.map(FnArg::parse)
|
||||
.collect::<syn::Result<Vec<_>>>()?;
|
||||
|
||||
let tp = if pass_module.is_some() {
|
||||
const PASS_MODULE_ERR: &str = "expected &PyModule as first argument with `pass_module`";
|
||||
ensure_spanned!(
|
||||
!arguments.is_empty(),
|
||||
func.span() => PASS_MODULE_ERR
|
||||
);
|
||||
let arg = arguments.remove(0);
|
||||
ensure_spanned!(
|
||||
type_is_pymodule(arg.ty),
|
||||
arg.ty.span() => PASS_MODULE_ERR
|
||||
);
|
||||
method::FnType::FnModule
|
||||
let span = match func.sig.inputs.first() {
|
||||
Some(syn::FnArg::Typed(first_arg)) => first_arg.ty.span(),
|
||||
Some(syn::FnArg::Receiver(_)) | None => bail_spanned!(
|
||||
func.sig.paren_token.span.join() => "expected `&PyModule` or `Py<PyModule>` as first argument with `pass_module`"
|
||||
),
|
||||
};
|
||||
method::FnType::FnModule(span)
|
||||
} else {
|
||||
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 {
|
||||
FunctionSignature::from_arguments_and_attribute(arguments, signature)?
|
||||
} else {
|
||||
|
@ -269,20 +270,3 @@ pub fn impl_wrap_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),
|
||||
None,
|
||||
)?),
|
||||
(_, FnType::FnClass) => GeneratedPyMethod::Method(impl_py_method_def(
|
||||
(_, FnType::FnClass(_)) => GeneratedPyMethod::Method(impl_py_method_def(
|
||||
cls,
|
||||
spec,
|
||||
&spec.get_doc(meth_attrs),
|
||||
|
@ -238,7 +238,7 @@ pub fn gen_py_method(
|
|||
Some(quote!(_pyo3::ffi::METH_STATIC)),
|
||||
)?),
|
||||
// special prototypes
|
||||
(_, FnType::FnNew) | (_, FnType::FnNewClass) => {
|
||||
(_, FnType::FnNew) | (_, FnType::FnNewClass(_)) => {
|
||||
GeneratedPyMethod::Proto(impl_py_method_def_new(cls, spec)?)
|
||||
}
|
||||
|
||||
|
@ -258,7 +258,7 @@ pub fn gen_py_method(
|
|||
doc: spec.get_doc(meth_attrs),
|
||||
},
|
||||
)?),
|
||||
(_, FnType::FnModule) => {
|
||||
(_, FnType::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 methoddef_type = match spec.tp {
|
||||
FnType::FnStatic => quote!(Static),
|
||||
FnType::FnClass => quote!(Class),
|
||||
FnType::FnClass(_) => quote!(Class),
|
||||
_ => quote!(Method),
|
||||
};
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_abi3() -> bool {
|
||||
pyo3_build_config::get().abi3
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3-macros"
|
||||
version = "0.20.0"
|
||||
version = "0.20.3"
|
||||
description = "Proc macros for PyO3 package"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||
|
@ -16,10 +16,11 @@ proc-macro = true
|
|||
[features]
|
||||
multiple-pymethods = []
|
||||
|
||||
abi3 = ["pyo3-macros-backend/abi3"]
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = { version = "1", default-features = false }
|
||||
quote = "1"
|
||||
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.
|
||||
|
||||
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use pyo3_macros_backend::{
|
||||
|
|
|
@ -21,6 +21,7 @@ classifiers = [
|
|||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
]
|
||||
|
|
|
@ -1,26 +1,9 @@
|
|||
[tool.black]
|
||||
target_version = ['py35']
|
||||
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.ruff.extend-per-file-ignores]
|
||||
"__init__.py" = ["F403"]
|
||||
|
||||
[tool.towncrier]
|
||||
filename = "CHANGELOG.md"
|
||||
version = "0.20.0"
|
||||
version = "0.20.3"
|
||||
start_string = "<!-- towncrier release notes start -->\n"
|
||||
template = ".towncrier.template.md"
|
||||
title_format = "## [{version}] - {project_date}"
|
||||
|
|
|
@ -15,3 +15,6 @@ pyo3-build-config = { path = "../pyo3-build-config" }
|
|||
[lib]
|
||||
name = "pyo3_pytests"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
@ -6,19 +6,17 @@ nox.options.sessions = ["test"]
|
|||
|
||||
@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:
|
||||
session.install("--only-binary=numpy", "numpy>=1.16")
|
||||
except CommandFailed:
|
||||
# No binary wheel for numpy available on this platform
|
||||
pass
|
||||
session.install("maturin")
|
||||
session.run_always("maturin", "develop")
|
||||
session.run("pytest", *session.posargs)
|
||||
|
||||
|
||||
@nox.session
|
||||
def bench(session: nox.Session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install(".")
|
||||
session.install(".[dev]")
|
||||
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 :: 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::{types::PyModule, Python};
|
||||
|
||||
#[pyclass]
|
||||
struct Eq(i64);
|
||||
|
|
|
@ -118,16 +118,15 @@ def test_time(args, kwargs):
|
|||
|
||||
|
||||
@given(t=st.times())
|
||||
def test_time(t):
|
||||
def test_time_hypothesis(t):
|
||||
act = rdt.get_time_tuple(t)
|
||||
exp = (t.hour, t.minute, t.second, t.microsecond)
|
||||
|
||||
assert act == exp
|
||||
|
||||
|
||||
@pytest.mark.xfail(PYPY, reason="Feature not available on PyPy")
|
||||
@given(t=st.times())
|
||||
def test_time_fold(t):
|
||||
def test_time_tuple_fold(t):
|
||||
t_nofold = t.replace(fold=0)
|
||||
t_fold = t.replace(fold=1)
|
||||
|
||||
|
@ -138,9 +137,8 @@ def test_time_fold(t):
|
|||
assert act == exp
|
||||
|
||||
|
||||
@pytest.mark.xfail(PYPY, reason="Feature not available on PyPy")
|
||||
@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)
|
||||
assert t.fold == fold
|
||||
|
||||
|
@ -206,7 +204,6 @@ def test_datetime_tuple(dt):
|
|||
assert act == exp
|
||||
|
||||
|
||||
@pytest.mark.xfail(PYPY, reason="Feature not available on PyPy")
|
||||
@given(dt=st.datetimes())
|
||||
def test_datetime_tuple_fold(dt):
|
||||
dt_fold = dt.replace(fold=1)
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import gc
|
||||
import platform
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from pyo3_pytests.objstore import ObjStore
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import platform
|
||||
|
||||
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,
|
||||
};
|
||||
use pyo3_ffi::{PyDateTime_IMPORT, PyTimeZone_FromOffset};
|
||||
use std::convert::TryInto;
|
||||
|
||||
impl ToPyObject for Duration {
|
||||
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