Skip to content

Commit b3a5b14

Browse files
author
Tom Dyas
committed
Merge branch 'main' into rwlockext_trait
2 parents 230cf6e + 2490163 commit b3a5b14

File tree

148 files changed

+3195
-1256
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

148 files changed

+3195
-1256
lines changed

.github/workflows/build.yml

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,10 @@ jobs:
126126
if: ${{ endsWith(inputs.python-version, '-dev') || (steps.ffi-changes.outputs.changed == 'true' && inputs.rust == 'stable' && !startsWith(inputs.python-version, 'graalpy') && !(inputs.python-version == 'pypy3.9' && contains(inputs.os, 'windows'))) }}
127127
run: nox -s ffi-check
128128

129-
- if: ${{ github.event_name != 'merge_group' }}
130-
name: Install cargo-llvm-cov
129+
- name: Install cargo-llvm-cov
131130
uses: taiki-e/install-action@cargo-llvm-cov
132131

133-
- if: ${{ github.event_name != 'merge_group' }}
134-
name: Prepare coverage environment
132+
- name: Prepare coverage environment
135133
run: |
136134
cargo llvm-cov clean --workspace --profraw-only
137135
nox -s set-coverage-env
@@ -148,8 +146,7 @@ jobs:
148146
env:
149147
CARGO_TARGET_DIR: ${{ github.workspace }}/target
150148

151-
- if: ${{ github.event_name != 'merge_group' }}
152-
name: Generate coverage report
149+
- name: Generate coverage report
153150
# needs investigation why llvm-cov fails on windows-11-arm
154151
continue-on-error: ${{ inputs.os == 'windows-11-arm' }}
155152
run: cargo llvm-cov
@@ -160,8 +157,7 @@ jobs:
160157
--package=pyo3-ffi
161158
report --codecov --output-path coverage.json
162159

163-
- if: ${{ github.event_name != 'merge_group' }}
164-
name: Upload coverage report
160+
- name: Upload coverage report
165161
uses: codecov/codecov-action@v5
166162
# needs investigation why llvm-cov fails on windows-11-arm
167163
continue-on-error: ${{ inputs.os == 'windows-11-arm' }}

.github/workflows/ci.yml

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ jobs:
145145
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
146146
matrix:
147147
rust: [stable]
148-
python-version: ["3.13"]
148+
python-version: ["3.14"]
149149
platform:
150150
[
151151
{
@@ -159,7 +159,7 @@ jobs:
159159
rust-target: "x86_64-unknown-linux-gnu",
160160
},
161161
{
162-
os: "ubuntu-22.04-arm",
162+
os: "ubuntu-24.04-arm",
163163
python-architecture: "arm64",
164164
rust-target: "aarch64-unknown-linux-gnu",
165165
},
@@ -173,12 +173,17 @@ jobs:
173173
python-architecture: "x86",
174174
rust-target: "i686-pc-windows-msvc",
175175
},
176+
{
177+
os: "windows-11-arm",
178+
python-architecture: "arm64",
179+
rust-target: "aarch64-pc-windows-msvc",
180+
},
176181
]
177182
include:
178183
# Test nightly Rust on PRs so that PR authors have a chance to fix nightly
179184
# failures, as nightly does not block merge.
180185
- rust: nightly
181-
python-version: "3.13"
186+
python-version: "3.14"
182187
platform:
183188
{
184189
os: "ubuntu-latest",
@@ -188,25 +193,7 @@ jobs:
188193
# Also test free-threaded Python just for latest Python version, on ubuntu
189194
# (run for all OSes on build-full)
190195
- rust: stable
191-
python-version: "3.13t"
192-
platform:
193-
{
194-
os: "ubuntu-latest",
195-
python-architecture: "x64",
196-
rust-target: "x86_64-unknown-linux-gnu",
197-
}
198-
# As we approach 3.14 release date, let's also have testing for 3.14 and 3.14t
199-
# on linux
200-
- rust: stable
201-
python-version: "3.14-dev"
202-
platform:
203-
{
204-
os: "ubuntu-latest",
205-
python-architecture: "x64",
206-
rust-target: "x86_64-unknown-linux-gnu",
207-
}
208-
- rust: stable
209-
python-version: "3.14t-dev"
196+
python-version: "3.14t"
210197
platform:
211198
{
212199
os: "ubuntu-latest",
@@ -243,8 +230,8 @@ jobs:
243230
"3.12",
244231
"3.13",
245232
"3.13t",
246-
"3.14-dev",
247-
"3.14t-dev",
233+
"3.14",
234+
"3.14t",
248235
"pypy3.9",
249236
"pypy3.10",
250237
"pypy3.11",
@@ -271,7 +258,7 @@ jobs:
271258
include:
272259
# Test minimal supported Rust version
273260
- rust: ${{ needs.resolve.outputs.MSRV }}
274-
python-version: "3.13"
261+
python-version: "3.14"
275262
platform:
276263
{
277264
os: "ubuntu-latest",
@@ -281,7 +268,7 @@ jobs:
281268

282269
# Test the `nightly` feature
283270
- rust: nightly
284-
python-version: "3.13"
271+
python-version: "3.14"
285272
platform:
286273
{
287274
os: "ubuntu-latest",
@@ -291,7 +278,7 @@ jobs:
291278

292279
# Run rust beta to help catch toolchain regressions
293280
- rust: beta
294-
python-version: "3.13"
281+
python-version: "3.14"
295282
platform:
296283
{
297284
os: "ubuntu-latest",
@@ -301,15 +288,15 @@ jobs:
301288

302289
# Test 32-bit Windows and x64 macOS only with the latest Python version
303290
- rust: stable
304-
python-version: "3.13"
291+
python-version: "3.14"
305292
platform:
306293
{
307294
os: "windows-latest",
308295
python-architecture: "x86",
309296
rust-target: "i686-pc-windows-msvc",
310297
}
311298
- rust: stable
312-
python-version: "3.13"
299+
python-version: "3.14"
313300
platform:
314301
{
315302
os: "macos-latest",
@@ -337,17 +324,17 @@ jobs:
337324
# python-architecture: "x64",
338325
# rust-target: "x86_64-apple-darwin",
339326
# }
340-
# arm64 Linux runner is in public preview, so test 3.13 on it
327+
# test latest Python on arm64 linux & windows runners
341328
- rust: stable
342-
python-version: "3.13"
329+
python-version: "3.14"
343330
platform:
344331
{
345-
os: "ubuntu-22.04-arm",
332+
os: "ubuntu-24.04-arm",
346333
python-architecture: "arm64",
347334
rust-target: "aarch64-unknown-linux-gnu",
348335
}
349336
- rust: stable
350-
python-version: "3.13"
337+
python-version: "3.14"
351338
platform:
352339
{
353340
os: "windows-11-arm",
@@ -701,6 +688,21 @@ jobs:
701688
env:
702689
CARGO_BUILD_TARGET: ${{ matrix.platform.rust-target }}
703690

691+
test-introspection-pr:
692+
needs: [fmt]
693+
if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-build-full') && github.event_name == 'pull_request' }}
694+
runs-on: ubuntu-latest
695+
steps:
696+
- uses: actions/checkout@v5
697+
- uses: dtolnay/rust-toolchain@stable
698+
with:
699+
components: rust-src
700+
- uses: actions/setup-python@v6
701+
with:
702+
python-version: "3.13"
703+
- run: python -m pip install --upgrade pip && pip install nox[uv]
704+
- run: nox -s test-introspection
705+
704706
conclusion:
705707
needs:
706708
- fmt

.github/workflows/coverage-pr-base.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
uses: ./.github/actions/fetch-merge-base
2222
with:
2323
base_ref: "refs/heads/${{ github.event.pull_request.base.ref }}"
24-
head_ref: "refs/pull/${{ github.event.pull_request.number }}/merge"
24+
head_ref: "refs/pull/${{ github.event.pull_request.number }}/head"
2525
- name: Set PR base on codecov
2626
run: |
2727
pip install codecov-cli

Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,13 @@ chrono = { version = "0.4.25", default-features = false, optional = true }
4848
chrono-tz = { version = ">= 0.10, < 0.11", default-features = false, optional = true }
4949
either = { version = "1.9", optional = true }
5050
eyre = { version = ">= 0.6.8, < 0.7", optional = true }
51-
hashbrown = { version = ">= 0.15.0, < 0.16", optional = true, default-features = false }
51+
hashbrown = { version = ">= 0.15.0, < 0.17", optional = true, default-features = false }
5252
indexmap = { version = ">= 2.5.0, < 3", optional = true }
5353
jiff-02 = { package = "jiff", version = "0.2", optional = true }
54-
num-bigint = { version = "0.4.2", optional = true }
54+
num-bigint = { version = "0.4.4", optional = true }
5555
num-complex = { version = ">= 0.4.6, < 0.5", optional = true }
5656
num-rational = { version = "0.4.1", optional = true }
57+
num-traits = { version = "0.2.16", optional = true }
5758
ordered-float = { version = "5.0.0", default-features = false, optional = true }
5859
rust_decimal = { version = "1.15", default-features = false, optional = true }
5960
time = { version = "0.3.38", default-features = false, optional = true }
@@ -135,10 +136,12 @@ py-clone = []
135136
parking_lot = ["dep:parking_lot", "lock_api"]
136137
arc_lock = ["lock_api", "lock_api/arc_lock", "parking_lot?/arc_lock"]
137138

139+
num-bigint = ["dep:num-bigint", "dep:num-traits"]
138140
bigdecimal = ["dep:bigdecimal", "num-bigint"]
139141

140142
chrono-local = ["chrono/clock", "dep:iana-time-zone"]
141143

144+
142145
# Optimizes PyObject to Vec conversion and so on.
143146
nightly = []
144147

@@ -203,6 +206,9 @@ let_unit_value = "warn"
203206
manual_assert = "warn"
204207
manual_ok_or = "warn"
205208
todo = "warn"
209+
# TODO: make this "warn"
210+
# https://github.com/PyO3/pyo3/issues/5487
211+
undocumented_unsafe_blocks = "allow"
206212
unnecessary_wraps = "warn"
207213
useless_transmute = "warn"
208214
used_underscore_binding = "warn"

Contributing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ PyO3 supports all officially supported Python versions, as well as the latest Py
184184
If you plan to add support for a pre-release version of CPython, here's a (non-exhaustive) checklist:
185185

186186
- [ ] Wait until the last alpha release (usually alpha7), since ABI is not guaranteed until the first beta release
187-
- [ ] Add prerelease_ver-dev (e.g. `3.14-dev`) to `.github/workflows/ci.yml`, and bump version in `noxfile.py`, `pyo3-ffi/Cargo.toml` under `max-version` within `[package.metadata.cpython]`, and `max` within `pyo3-ffi/build.rs`
187+
- [ ] Add prerelease_ver-dev (e.g. `3.14-dev`) to `.github/workflows/ci.yml`, and bump version in `noxfile.py`, `pyo3-ffi/Cargo.toml` under `max-version` within `[package.metadata.cpython]`, and `max` within `pyo3-ffi/build.rs`
188188
- [ ] Add a new abi3-prerelease feature for the version (e.g. `abi3-py314`)
189189
- In `pyo3-build-config/Cargo.toml`, set abi3-most_current_stable to ["abi3-prerelease"] and abi3-prerelease to ["abi3"]
190190
- In `pyo3-ffi/Cargo.toml`, set abi3-most_current_stable to ["abi3-prerelease", "pyo3-build-config/abi3-most_current_stable"] and abi3-prerelease to ["abi3", "pyo3-build-config/abi3-prerelease"]

README.md

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,21 +77,18 @@ pyo3 = { version = "0.26.0", features = ["extension-module"] }
7777
**`src/lib.rs`**
7878

7979
```rust
80-
use pyo3::prelude::*;
81-
82-
/// Formats the sum of two numbers as string.
83-
#[pyfunction]
84-
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
85-
Ok((a + b).to_string())
86-
}
87-
88-
/// A Python module implemented in Rust. The name of this function must match
80+
/// A Python module implemented in Rust. The name of this module must match
8981
/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
9082
/// import the module.
91-
#[pymodule]
92-
fn string_sum(m: &Bound<'_, PyModule>) -> PyResult<()> {
93-
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
94-
Ok(())
83+
#[pyo3::pymodule]
84+
mod string_sum {
85+
use pyo3::prelude::*;
86+
87+
/// Formats the sum of two numbers as string.
88+
#[pyfunction]
89+
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
90+
Ok((a + b).to_string())
91+
}
9592
}
9693
```
9794

emscripten/pybuilddir.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

guide/pyclass-parameters.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
| `eq_int` | Implements `__eq__` using `__int__` for simple enums. |
1010
| <span style="white-space: pre">`extends = BaseType`</span> | Use a custom baseclass. Defaults to [`PyAny`][params-1] |
1111
| <span style="white-space: pre">`freelist = N`</span> | Implements a [free list][params-2] of size N. This can improve performance for types that are often created and deleted in quick succession. Profile your code to see whether `freelist` is right for you. |
12+
| `from_py_object` | Implement `FromPyObject` for this pyclass. Requires the pyclass to be `Clone`. |
1213
| <span style="white-space: pre">`frozen`</span> | Declares that your pyclass is immutable. It removes the borrow checker overhead when retrieving a shared reference to the Rust struct, but disables the ability to get a mutable reference. |
1314
| `generic` | Implements runtime parametrization for the class following [PEP 560](https://peps.python.org/pep-0560/). |
1415
| `get_all` | Generates getters for all fields of the pyclass. |
@@ -21,6 +22,7 @@
2122
| `rename_all = "renaming_rule"` | Applies renaming rules to every getters and setters of a struct, or every variants of an enum. Possible values are: "camelCase", "kebab-case", "lowercase", "PascalCase", "SCREAMING-KEBAB-CASE", "SCREAMING_SNAKE_CASE", "snake_case", "UPPERCASE". |
2223
| `sequence` | Inform PyO3 that this class is a [`Sequence`][params-sequence], and so leave its C-API mapping length slot empty. |
2324
| `set_all` | Generates setters for all fields of the pyclass. |
25+
| `skip_from_py_object` | Prevents this PyClass from participating in the `FromPyObject: PyClass + Clone` blanket implementation. This allows a custom `FromPyObject` impl, even if `self` is `Clone`. |
2426
| `str` | Implements `__str__` using the `Display` implementation of the underlying Rust datatype or by passing an optional format string `str="<format string>"`. *Note: The optional format string is only allowed for structs. `name` and `rename_all` are incompatible with the optional format string. Additional details can be found in the discussion on this [PR](https://github.com/PyO3/pyo3/pull/4233).* |
2527
| `subclass` | Allows other Python classes and `#[pyclass]` to inherit from this class. Enums cannot be subclassed. |
2628
| `unsendable` | Required if your struct is not [`Send`][params-3]. Rather than using `unsendable`, consider implementing your struct in a thread-safe way by e.g. substituting [`Rc`][params-4] with [`Arc`][params-5]. By using `unsendable`, your class will panic when accessed by another thread. Also note the Python's GC is multi-threaded and while unsendable classes will not be traversed on foreign threads to avoid UB, this can lead to memory leaks. |

guide/src/conversions/traits.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ enum RustyEnum {
464464
```
465465

466466
If the input is neither a string nor an integer, the error message will be:
467-
`"'<INPUT_TYPE>' cannot be converted to 'str | int'"`.
467+
`"'<INPUT_TYPE>' cannot be cast as 'str | int'"`.
468468

469469
#### `#[derive(FromPyObject)]` Container Attributes
470470
- `pyo3(transparent)`
@@ -535,6 +535,31 @@ struct RustyStruct {
535535
# }
536536
```
537537

538+
#### ⚠ Phase-Out of `FromPyObject` blanket implementation for cloneable PyClasses ⚠
539+
Historically PyO3 has provided a blanket implementation for `#[pyclass]` types that also implement `Clone`, to allow extraction of such types by value. Over time this has turned out problematic for a few reasons, the major one being the prevention of custom conversions by downstream crates if their type is `Clone`. Over the next few releases the blanket implementation is gradually phased out, and eventually replaced by an opt-in option. As a first step of this migration a new `skip_from_py_object` option for `#[pyclass]` was introduced, to opt-out of the blanket implementation and allow downstream users to provide their own implementation:
540+
```rust
541+
# #![allow(dead_code)]
542+
# use pyo3::prelude::*;
543+
544+
#[pyclass(skip_from_py_object)] // opt-out of the PyO3 FromPyObject blanket
545+
#[derive(Clone)]
546+
struct Number(i32);
547+
548+
impl<'py> FromPyObject<'_, 'py> for Number {
549+
type Error = PyErr;
550+
551+
fn extract(obj: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
552+
if let Ok(obj) = obj.cast::<Self>() { // first try extraction via class object
553+
Ok(obj.borrow().clone())
554+
} else {
555+
obj.extract::<i32>().map(Self) // otherwise try integer directly
556+
}
557+
}
558+
}
559+
```
560+
561+
As a second step the `from_py_object` option was introduced. This option also opts-out of the blanket implementation and instead generates a custom `FromPyObject` implementation for the pyclass which is functionally equivalent to the blanket.
562+
538563
### `IntoPyObject`
539564
The [`IntoPyObject`] trait defines the to-python conversion for a Rust type. All types in PyO3 implement this trait,
540565
as does a `#[pyclass]` which doesn't use `extends`.

guide/src/features.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ Adds a dependency on [jiff@0.2](https://docs.rs/jiff/0.2) and requires MSRV 1.70
172172
- [DateTime](https://docs.rs/jiff/0.2/jiff/civil/struct.DateTime.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html)
173173
- [Zoned](https://docs.rs/jiff/0.2/jiff/struct.Zoned.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html)
174174
- [Timestamp](https://docs.rs/jiff/0.2/jiff/struct.Timestamp.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html)
175+
- [ISOWeekDate](https://docs.rs/jiff/0.2/jiff/civil/struct.ISOWeekDate.html) -> [`PyDate`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDate.html)
175176

176177
### `lock_api`
177178

0 commit comments

Comments
 (0)