Compare commits

...

69 Commits

Author SHA1 Message Date
Paul Stemmet 338c71d0ad
src/impl_: add ff unsafe-allow-subinterpreters
Allow the subinterpreter safeguards to be disabled, so that applications
like Ceph's manager can continue to use pyo3 modules without soft
crashing.

Enabling this feature should be done with caution, as any storage of Py
objects in rust statics can lead to undefined behavior.

However, not all consumers of pyo3 use global state, and thus a subset
of them (such as python-bcrypt) are safe to use in subinterpreter
contexts.

References: https://github.com/bazaah/aur-ceph/issues/20
References: https://github.com/PyO3/pyo3/pull/2523
References: https://github.com/pyca/cryptography/issues/9016
References: https://github.com/PyO3/pyo3/discussions/2346#discussioncomment-3246505
References: https://github.com/PyO3/pyo3/discussions/2346#discussioncomment-2911159
References: https://github.com/PyO3/pyo3/issues/3451
Signed-off-by: Paul Stemmet <github@luxolus.com>
2024-03-16 16:12:25 +00:00
David Hewitt a22e4f6005 release: 0.20.3 2024-02-23 11:59:20 +00:00
David Hewitt 5f65a97bdc ci: workaround more aggressive nightly rust lints 2024-02-23 11:59:20 +00:00
David Hewitt 2d5a54e4d2 ci: updates for Rust 1.76 2024-02-23 11:59:20 +00:00
David Hewitt 3447f0e77d fix `either` feature conditional compilation, again (#3834)
* fix `either` feature conditional compilation, again

* test feature powerset in CI

* install `rust-src` for feature powerset tests

* review: adamreichold feedback

* Fix one more case of redundant imports.

* just check feature powerset for now

---------

Co-authored-by: Adam Reichold <adam.reichold@t-online.de>
2024-02-23 11:59:20 +00:00
David Hewitt 5157a45b39 add maximum Python version check (#3821)
* add maximum Python version check

* restore dependency of `pyo3-macros-backend` on `pyo3-build-config`

* fix clippy-all noxfile job
2024-02-23 11:59:20 +00:00
Adam Reichold f7afb8157d Use portable-atomic for targets which lack 64-bit atomics used to check interpreter ID.
I chose to make the dependency mandatory instead of optional as portable-atomic
itself just forwards to the native atomics when they are available so making
that choice part of our build system is not really necessary. Personally, I was
unable to perceive any noticeable compile-time hit from adding it.
2024-02-23 11:59:20 +00:00
David Hewitt 4774ded49a
Merge pull request #3725 from PyO3/release-0.20.2
release: 0.20.2
2024-01-04 21:31:43 +00:00
David Hewitt bcef18b988 release: 0.20.2 2024-01-03 13:20:28 +00:00
Adam Reichold fa6d60b77e Use a definite version specification when depending on pyo3-build-config.
We already do this for other internal pyo3-* dependencies and it seems prudent
to apply this to pyo3-build-config as well.
2024-01-03 13:14:34 +00:00
Adam Reichold f9f0bdde70
Merge pull request #3724 from davidhewitt/fix-build-config-issue
re-add emit_pyo3_cfgs for pyo3 0.20.0 compatibility
2024-01-03 13:42:40 +01:00
David Hewitt cf213252fa re-add emit_pyo3_cfgs for pyo3 0.20.0 compatibility 2024-01-02 20:58:30 +00:00
David Hewitt f7893858d2
Merge pull request #3722 from PyO3/fix-doc-build
Fix missing feature flags in implementation of Either conversion.
2024-01-02 19:52:53 +00:00
Adam Reichold 9120b35f35
Include the experimental-inspect feature for the docs.rs build thereby making it equivalent to a full build. 2024-01-02 18:55:13 +01:00
Adam Reichold 2e79c557cc
Add CI job to test the equivalent of a docs.rs build. 2024-01-02 13:29:06 +01:00
Adam Reichold 2564ca4e75
Fix missing feature flags in implementation of Either conversion. 2024-01-02 09:52:45 +01:00
David Hewitt be4d5627a3
Merge pull request #3713 from PyO3/release-0.20.1
release: 0.20.1
2023-12-30 21:31:58 +00:00
David Hewitt d3f034a80f release: 0.20.1 2023-12-30 21:20:06 +00:00
David Hewitt 985412fb8f ci: updates for Rust 1.75 2023-12-29 23:02:19 +00:00
Adam Reichold ecb0e9cb61 Copy note on using check_signals on non-main thread/interpreter from Python docs. 2023-12-29 22:59:45 +00:00
Alex Gaynor b84271140e Fixes #3645 -- added `abi3-py312` feature 2023-12-29 22:59:22 +00:00
Adam Reichold d897479831 Fix the Crossbeam ecosystem to point releases before it required Rust 1.61. 2023-12-29 22:52:52 +00:00
Nathan Kent 8f6976d9a5 Enable `GILProtected` access via `PyVisit`
Closes #3615

This simply adds a new method which uses the existence of a `PyVisit`
object as proof that the GIL is held instead of a `Python` object. This
allows `GILProtected` to be used in instances where contained Python
objects need to participate in garbage collection. Usage in this
situation should be valid since no Python calls are made and this does
not provide any additional mechanism for accessing a `Python` object.
2023-12-29 22:52:22 +00:00
David Hewitt 5c1e4d10b3 ci: fixup pytests to compile in debug 2023-12-29 22:52:10 +00:00
David Hewitt 1896a32015 ci: refactor pytests dev dependencies 2023-12-29 22:51:45 +00:00
messense 7032789daf Add additional definitions for `_PyImport_Frozen*` 2023-12-29 22:50:38 +00:00
Alex Gaynor 1166a995a4 Refactor create_type_object so that most of the code is monomorphic
In pyca/cryptography this function is the #1 source of lines of generated LLVM IR, because it is duplicated 42x (and growing!). By rewriting it so most of the logic is monomorphic, we reduce the generated LLVM IR for this function by 4x.
2023-12-29 22:50:23 +00:00
David Hewitt 856b859efe fix test-serde beta clippy warning 2023-12-29 22:50:15 +00:00
David Hewitt 413dda09f5 fix pyo3-ffi beta clippy warnings 2023-12-29 22:50:08 +00:00
David Hewitt 485f5c00e2 ci: run beta clippy as an allowed-to-fail job 2023-12-29 22:50:01 +00:00
Alex Gaynor 830b3bb814 fixes #3561 -- silence new clippy warning 2023-12-29 22:49:42 +00:00
mejrs 405d722a2d Create subinterpreter example 2023-12-29 22:49:25 +00:00
David Hewitt b1de927a31 docs: fixup docs for smallvec feature 2023-12-29 22:49:13 +00:00
Ivan Smirnov 2312270ec1 add conversion support for `either::Either` 2023-12-29 22:49:07 +00:00
David Hewitt e0513d74f5 improve error for invalid `#[classmethod]` receivers 2023-12-29 22:48:25 +00:00
David Hewitt 25b8a37521 remove type_is_pymodule 2023-12-29 22:47:39 +00:00
Joseph Perez 3d17f7442a fix: replace removed `fmt` session by `rustfmt` and `ruff` 2023-12-29 22:47:27 +00:00
Joseph Perez 466359a1c8 feat: allow `classmethod`/`pass_module` to receive owned types
This is necessary for async functions
2023-12-29 22:47:16 +00:00
David Hewitt 9dbd81b47c enable cargo-semver-checks, try 2 2023-12-29 22:46:49 +00:00
David Hewitt bbc5404297 ci: move lints to new 1.74 cargo.toml tables 2023-12-29 22:46:39 +00:00
David Hewitt 8d637b0b5b ci: updates for rust 1.74 2023-12-29 22:46:29 +00:00
David Hewitt aa6f1466d2 ci: try to run without rust-toolchain.toml 2023-12-29 22:46:23 +00:00
Surya c8ef081821 fix missing char conversion 2023-12-29 22:45:43 +00:00
David Hewitt d649f6603f add link to user guide to cross compile error message 2023-12-29 22:45:35 +00:00
Joseph Perez b08c92b306 docs: add newsfragment 2023-12-29 22:45:27 +00:00
Joseph Perez a4aba0a09a refactor: remove useless `unsafe` in `get_mut` 2023-12-29 22:45:20 +00:00
Joseph Perez 04bb9f2110 feat: add `take` and `into_inner` methods to `GILOnceCell` 2023-12-29 22:45:12 +00:00
Samuel Pastva 10086d176a Finish rename 2023-12-29 22:44:49 +00:00
Samuel Pastva 15c280015d Update guide/src/class.md
Co-authored-by: Bruno Kolenbrander <59372212+mejrs@users.noreply.github.com>
2023-12-29 22:44:43 +00:00
Samuel Pastva 2aca7f53f0 Add example of dynamic return type in the "Python classes" section of the guide. 2023-12-29 22:44:33 +00:00
David Hewitt 04af02f155 ci: switch from black to ruff 2023-12-29 22:44:24 +00:00
David Hewitt 8e08e4ad1b examples: remove requirements-dev.txt files 2023-12-29 22:44:11 +00:00
David Hewitt 7f328767a3 Note about `pyenv activate` and `pyenv virtualenv` commands
Co-authored-by: Niko Matsakis <niko@alum.mit.edu>
2023-12-29 22:44:06 +00:00
David Hewitt ba5a1da4a8 ci: fix nightly unused import warnings 2023-12-29 22:43:56 +00:00
David Hewitt 410ef89456 docs: improve detail around pyenv install 2023-12-29 22:43:51 +00:00
David Hewitt 92cde096b5 add coverage for `emit_pyo3_cfgs` 2023-12-29 22:43:44 +00:00
David Hewitt 8c272a6ef2 ci: tidy up some dev deps 2023-12-29 22:43:32 +00:00
David Hewitt e900df02f0 ci: use older hashbrown and indexmap for MSRV 2023-12-29 22:42:35 +00:00
dependabot[bot] 97bf194152 Bump actions/setup-node from 3 to 4
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-29 22:42:25 +00:00
David Hewitt 422f8665c9 ci: install prebuilt cargo-careful 2023-12-29 22:41:07 +00:00
David Hewitt 30463b6720 remove comparison to rust-cpython 2023-12-29 22:41:00 +00:00
David Hewitt f745299b7b ci: drop psutil dependency 2023-12-29 22:40:49 +00:00
Adam Reichold d468f570ae Align chrono dev and runtime dependency version specifications. 2023-12-29 22:40:02 +00:00
Orhun Parmaksız 826fa973b6 Set version of smallvec to 1.0 2023-12-29 22:39:53 +00:00
Orhun Parmaksız 779eb2412c Add an entry to features table in lib 2023-12-29 22:39:43 +00:00
David Hewitt 84264b358e keep emscripten back on 3.11 for now 2023-12-29 22:39:36 +00:00
David Hewitt 9e07203afb also test emscripten with CI-build-full 2023-12-29 22:39:26 +00:00
David Hewitt 8392ed2a94 bump "latest" CI jobs to 3.12 2023-12-29 22:39:17 +00:00
Orhun Parmaksız 427b2e9386 Add support for `SmallVec` in conversion traits (#3440) 2023-12-29 22:39:09 +00:00
134 changed files with 1995 additions and 613 deletions

View File

@ -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"
]

View File

@ -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

View File

@ -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:

1
.gitignore vendored
View File

@ -23,3 +23,4 @@ valgrind-python.supp
*.pyd
lcov.info
netlify_build/
.nox/

View File

@ -1 +1 @@
3.11
3.12

View File

@ -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

View File

@ -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

View File

@ -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"]
```

View File

@ -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();

1
emscripten/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
pybuilddir.txt

View File

@ -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"

View File

@ -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

View File

@ -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");

View File

@ -5,3 +5,6 @@ build-backend = "maturin"
[project]
name = "{{project-name}}"
version = "0.1.0"
[project.optional-dependencies]
dev = ["pytest"]

View File

@ -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")

View File

@ -14,3 +14,6 @@ classifiers = [
"Operating System :: POSIX",
"Operating System :: MacOS :: MacOS X",
]
[project.optional-dependencies]
dev = ["pytest"]

View File

@ -1,3 +0,0 @@
pytest>=3.5.0
pip>=21.3
maturin>=0.12,<0.13

View File

@ -1,3 +1,6 @@
from decorator import Counter
@Counter
def say_hello():
print("hello")

View File

@ -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()

View File

@ -5,3 +5,6 @@ build-backend = "maturin"
[project]
name = "{{project-name}}"
version = "0.1.0"
[project.optional-dependencies]
dev = ["pytest"]

View File

@ -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")

View File

@ -14,3 +14,6 @@ classifiers = [
"Operating System :: POSIX",
"Operating System :: MacOS :: MacOS X",
]
[project.optional-dependencies]
dev = ["pytest"]

View File

@ -1,3 +0,0 @@
pytest>=3.5.0
pip>=21.3
maturin>=1,<2

View File

@ -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");

View File

@ -5,3 +5,6 @@ build-backend = "maturin"
[project]
name = "{{project-name}}"
version = "0.1.0"
[project.optional-dependencies]
dev = ["pytest"]

View File

@ -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"]

View File

@ -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")

View File

@ -14,3 +14,6 @@ classifiers = [
"Operating System :: POSIX",
"Operating System :: MacOS :: MacOS X",
]
[project.optional-dependencies]
dev = ["pytest"]

View File

@ -1,3 +0,0 @@
pytest>=3.5.0
pip>=21.3
maturin>=0.12,<0.13

View File

@ -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");

View File

@ -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")

View File

@ -10,3 +10,6 @@ classifiers = [
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
[project.optional-dependencies]
dev = ["pytest"]

View File

@ -1,3 +0,0 @@
pytest>=3.5.0
pip>=21.3
maturin>=0.14

View File

@ -1,2 +1,2 @@
def test_import():
import plugin_api
import plugin_api # noqa: F401

View File

@ -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"] }

View File

@ -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");

View File

@ -0,0 +1,7 @@
[build-system]
requires = ["maturin>=1,<2"]
build-backend = "maturin"
[project]
name = "{{project-name}}"
version = "0.1.0"

View File

@ -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]

View File

@ -0,0 +1,2 @@
include pyproject.toml Cargo.toml
recursive-include src *

View File

@ -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.)

View File

@ -0,0 +1,5 @@
[template]
ignore = [".nox"]
[hooks]
pre = [".template/pre-script.rhai"]

View File

@ -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")

View File

@ -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"]

View File

@ -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 },
};

View File

@ -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))
}

View File

@ -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,
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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");

View File

@ -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"}

View File

@ -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"]

View File

@ -5,3 +5,6 @@ build-backend = "maturin"
[project]
name = "{{project-name}}"
version = "0.1.0"
[project.optional-dependencies]
dev = ["pytest"]

View File

@ -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")

View File

@ -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"]

View File

@ -1,3 +0,0 @@
pytest>=3.5.0
pip>=21.3
maturin>=0.12,<0.13

View File

@ -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)

View File

@ -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");

View File

@ -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"

View File

@ -1,9 +0,0 @@
[metadata]
name = {{project-name}}
version = 0.1.0
packages =
word_count
[options]
include_package_data = True
zip_safe = False

View File

@ -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")

View File

@ -15,6 +15,8 @@ classifiers = [
"Operating System :: MacOS :: MacOS X",
]
[project.optional-dependencies]
dev = ["pytest", "pytest-benchmark"]
[tool.pytest.ini_options]
addopts = "--benchmark-disable"

View File

@ -1,2 +0,0 @@
pytest>=3.5.0
pytest-benchmark>=3.1.1

View File

@ -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)
---

View File

@ -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)");
# });
```

View File

@ -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` |

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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()
env = os.environ.copy()
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"

View File

@ -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"]

View File

@ -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)+))
};
}

View File

@ -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(
"PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified \
when cross-compiling and PYO3_CROSS_LIB_DIR is not set.",
.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.\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(),
]
);
}
}

View File

@ -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.

View File

@ -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

View File

@ -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<()> {
ensure!(
interpreter_config.version >= MINIMUM_SUPPORTED_VERSION,
"the configured Python interpreter version ({}) is lower than PyO3's minimum supported version ({})",
interpreter_config.version,
MINIMUM_SUPPORTED_VERSION,
);
// 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 >= versions.min,
"the configured Python interpreter version ({}) is lower than PyO3's minimum supported version ({})",
interpreter_config.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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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::*;

View File

@ -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;

View File

@ -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)))]

View File

@ -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

View File

@ -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(_)]) {
FnType::FnNew
} else {
FnType::FnNewClass
}
[MethodTypeAttribute::New(_)] => {
set_name_to_new()?;
FnType::FnNew
}
[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,
};

View File

@ -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
}

View File

@ -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);

View File

@ -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
}

View File

@ -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

View File

@ -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::{

View File

@ -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",
]

View File

@ -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}"

View File

@ -15,3 +15,6 @@ pyo3-build-config = { path = "../pyo3-build-config" }
[lib]
name = "pyo3_pytests"
crate-type = ["cdylib"]
[lints]
workspace = true

View File

@ -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)

View File

@ -1 +0,0 @@
from .pyo3_pytests import *

View File

@ -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"
]

View File

@ -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

View File

@ -1,5 +1,4 @@
use pyo3::prelude::*;
use pyo3::{types::PyModule, Python};
#[pyclass]
struct Eq(i64);

View File

@ -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)

View File

@ -1,8 +1,6 @@
import gc
import platform
import sys
import pytest
from pyo3_pytests.objstore import ObjStore

View File

@ -1,5 +1,3 @@
import platform
from pyo3_pytests.subclassing import Subclassable

View File

@ -1,2 +0,0 @@
[toolchain]
components = [ "rust-src" ]

View File

@ -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