Compare commits

...

156 Commits

Author SHA1 Message Date
David Hewitt a5a3f3f7f2
allow `#[pymodule(...)]` to accept all relevant `#[pyo3(...)]` options (#4330) 2024-07-10 22:38:38 +00:00
Larry Z 6be80647cb
Prevent building in GIL-less environment (#4327)
* Prevent building in GIL-less environment

* Add change log

* add "yet" to phrasing

* Add testing to build script

* add link to issue

* Fix formatting issues

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
2024-07-10 18:39:39 +00:00
David Hewitt 90c4799951
docs: fixups to 0.22 migration guide (#4332) 2024-07-10 14:18:13 +00:00
David Hewitt 3c65132da5
remove all functionality deprecated in PyO3 0.21 (#4323)
* remove all functionality deprecated in PyO3 0.21

* further adjustments after removing deprecated APIs
2024-07-10 13:38:30 +00:00
David Hewitt 32b6a1aef1
docs: use versioned links from docs to guide (#4331)
* use versioned links from docs to guide

* use standard python version in `guide-build` ci job
2024-07-10 12:30:01 +00:00
David Hewitt c7c1dff710
ci: check minimal-versions on MSRV feature powerset (#4273)
* ci: check minimal-versions on MSRV feature powerset

* fix msrv arg

* bump num-bigint floor to 0.4.2

* try fix build syntax
2024-07-10 11:36:31 +00:00
Giovanni Barillari 1279232701
Add Granian to example projects (#4329) 2024-07-10 09:16:23 +00:00
David Hewitt 97d60e869b
clean up hygiene tests (#4328) 2024-07-09 21:28:15 +00:00
David Hewitt 8652ac8e1c
remove all functionality deprecated in 0.20 (#4322)
* remove all functionality deprecated in 0.20

* bump version to 0.23.0-dev

* undo unintended revert

* fixup UI test
2024-07-09 16:44:27 +00:00
Alex Gaynor e73112f3f6
Automatically treat nested modules as submodules (#4308)
fixes #4286
2024-07-09 12:15:12 +00:00
David Hewitt 186c7d3315
docs: improve signposting to bound and traits (#4325)
* docs: improve signposting to bound and traits

* update UI tests
2024-07-09 11:53:11 +00:00
Sede Soukossi 1861d6d379
docs: Fix mismatch return value, remove redundant error propagation, and additional fixes (#4318)
* Fix mismatch return value, remove redundant error propagation, and fix typo

* Update guide/src/function/signature.md

Co-authored-by: Lily Foote <code@lilyf.org>

---------

Co-authored-by: Lily Foote <code@lilyf.org>
2024-07-08 20:30:44 +00:00
Icxolu 3c155d9fef
remove the gil-ref deprecations infrastructure (#4320) 2024-07-08 13:40:27 +00:00
David Hewitt d5c886f4c0
simplify implementation of `Py::clone_ref` (#4313) 2024-07-07 06:53:43 +00:00
David Hewitt 59c4fa3f24
release: 0.22.1 (#4314) 2024-07-06 22:00:28 +00:00
Alex Gaynor 9afc38ae41
fixes #4285 -- allow full-path to pymodule with nested declarative modules (#4288) 2024-07-05 09:16:06 +00:00
Owen Leung 5860c4f7e9
implement PartialEq for Pybool & bool (#4305)
* implement PartialEq for Pybool

implement PartialEq for Pybool

* fix failing cargodoc and add changelog file

* Use PR number for change log file

* Add false checking into test
2024-07-05 09:10:38 +00:00
David Hewitt 0af0227834
fix deprecation warning for trailing optional on `#[setter]` functions (#4304)
* fix deprecation warning for trailing optional on `#[setter]` functions

* add comment
2024-07-04 10:08:22 +00:00
Nathan Goldbaum ee9123a2d2
Fix link in the contribution guide (#4306) 2024-07-02 19:28:26 +00:00
Alex Gaynor ccd04475a3
refs #4286 -- allow setting submodule on declarative pymodules (#4301) 2024-07-02 11:24:47 +00:00
Alex Gaynor f3603a0a48
Avoid generating functions that are only ever const evaluated with declarative modules (#4297)
Refs #4286
2024-07-01 21:54:50 +00:00
Kyle Barron 872bd7e6f7
Add pyo3-arrow to README (#4302) 2024-07-01 16:26:17 +00:00
Ben Beasley 8f7450e33d
Fix 128-bit int regression on big-endian with Python <3.13 (#4291)
Fixes #4290.
2024-06-26 19:21:31 +00:00
jatoben 7c2f5e80de
Don't raise `TypeError` from generated equality method (#4287)
* Don't raise TypeError in derived equality method

* Add newsfragment
2024-06-26 05:41:42 +00:00
David Hewitt 2e2d4404a6
release: 0.22.0 (#4266) 2024-06-24 19:06:42 +00:00
David Hewitt 91d8683814
improve deprecation message on implicit trailing optionals (#4282) 2024-06-24 13:38:33 +00:00
Bruno Kolenbrander 6a0221ba2c
document FnType and refactor FnType::self_arg (#4276) 2024-06-23 10:36:19 +00:00
Aneesh Agrawal c67625d683
Revamp PyType name functions to match PEP 737 (#4196)
* Revamp PyType name functions to match PEP 737

PyType::name uses `tp_name`, which is not consistent.
[PEP 737](https://peps.python.org/pep-0737/) adds a new path forward,
so update PyType::name and add PyType::{module,fully_qualified_name}
to match the PEP.

* refactor conditional code to handle multiple Python versions better

* return `Bound<'py, str>`

* fixup

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
2024-06-22 22:10:27 +00:00
David Hewitt a2f9399906
make datetime from timestamp tests compare against Python result (#4275)
* attemp to fix range for st.datetime

* remove example

* try fixing to utc

* make datetime from timestamp tests compare against Python result

---------

Co-authored-by: Cheukting <cheukting.ho@gmail.com>
2024-06-22 22:09:59 +00:00
Code Apprentice 908ef6ad84
Implement PartialEq for PyBytes and [u8] (#4259)
* Copy pasta implementation from types/string.rs

* changelog

* I think I don't need a special implementation for 3.10 or ABI

* Copy pasta tests

* Fix comment with correct type

Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>

* Fix implementation

* Use slice in tests

* Try renaming changelog file

* Fix doc example

* Fix doc example

Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>

---------

Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>
2024-06-22 22:08:57 +00:00
Weijie Guo c4d18e5ee3
Change `search_lib_dir`'s return type to Result (#4043)
* Change `search_lib_dir`'s return type to Result

* add changelog

* add coverage and a hint to `PYO3_CROSS_LIB_DIR`

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
2024-06-22 14:01:33 +00:00
David Hewitt 0b967d427a
use `ffi::MemberGef` for `#[pyo3(get)]` fields of `Py<T>` (#4254)
* use `ffi::MemberGef` for `#[pyo3(get)]` fields of `Py<T>`

* tidy up implementation

* make it work on MSRV :(

* fix docs and newsfragment

* clippy

* internal docs and coverage

* review: mejrs
2024-06-21 23:33:34 +00:00
David Hewitt 41fb9572b6
docs: update docstring on `Python` for `Bound` API (#4274) 2024-06-21 11:30:57 +00:00
Alex Gaynor 9ff3d237c1
Update dependencies to reflect minimal versions (#4272)
Based on testing locally with `rm -f Cargo.lock && cargo +nightly check --tests --all -Z minimal-versions`
2024-06-21 10:05:09 +00:00
Icxolu 56341cbc81
emit c-string literals on Rust 1.77 or later (#4269)
* emit c-string literals on Rust 1.77 or later

* only clone `PyO3CratePath` instead of the whole `Ctx`

Co-authored-by: mejrs <59372212+mejrs@users.noreply.github.com>

---------

Co-authored-by: mejrs <59372212+mejrs@users.noreply.github.com>
Co-authored-by: David Hewitt <mail@davidhewitt.dev>
2024-06-21 08:49:33 +00:00
David Hewitt ca82681615
ci: minor cleanups following 1.63 MSRV (#4239)
* ci: minor cleanups following 1.63 MSRV

* correct `invalid_pymethods_duplicates` UI test

* fix `nightly` feature
2024-06-21 07:02:31 +00:00
Bruno Kolenbrander a983b2fe7b
Bump diagnostic_namespace rust version (#4268) 2024-06-20 21:56:17 +00:00
David Hewitt 30add032b5
improve code generated by `c_str!` macro (#4270)
* improve code generated by `c_str!` macro

* fix clippy
2024-06-20 21:41:16 +00:00
Bruno Kolenbrander b25b3b3a7b
Improve the span and message for return types of pymethod/functions (#4220)
* Improve the span and message for return types of pymethod/functions

* Don't pass the span

* fixup trybuild output

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
2024-06-20 09:23:40 +00:00
WÁNG Xuěruì e6b2216b04
Add several missing wrappers to PyAnyMethods (#4264) 2024-06-20 08:16:06 +00:00
David Hewitt 0e142f05dd
add `c_str!` macro to create `&'static CStr` (#4255)
* add `c_str!` macro to create `&'static CStr`

* newsfragment, just export as `pyo3::ffi::c_str`

* fix doc link

* fix doc

* further `c_str!` based cleanups

* [review]: mejrs

Co-authored-by: Bruno Kolenbrander <59372212+mejrs@users.noreply.github.com>

* rustfmt

* build fixes

* clippy

* allow lint on MSRV

* fix GraalPy import

---------

Co-authored-by: Bruno Kolenbrander <59372212+mejrs@users.noreply.github.com>
2024-06-18 18:09:36 +00:00
Alex Gaynor ddff8bea25
Stabilize declarative modules (#4257) 2024-06-17 01:28:20 +00:00
Alex Gaynor baae9291cc
Update tests for numpy 2.0 (#4258) 2024-06-17 00:05:55 +00:00
Code Apprentice 79591f2161
Add error messages for unsupported macro features on compilation (#4194)
* First implementation

* tweak error message wording

* Fix boolean logic

* Remove redundant parens

* Add test for weakref error

* Fix test

* Reword error message

* Add expected error output

* Rinse and repeat for `dict`

* Add test output file

* Ignore Rust Rover config files

* cargo fmt

* Add newsfragment

* Update newsfragments/4194.added.md

Co-authored-by: David Hewitt <mail@davidhewitt.dev>

* Use ensure_spanned! macro

Co-authored-by: David Hewitt <mail@davidhewitt.dev>

* Use ensure_spanned! macro for weakref error, too

Co-authored-by: David Hewitt <mail@davidhewitt.dev>

* Revert "Ignore Rust Rover config files"

This reverts commit 6c8a2eec581ed250ec792d8465772d649b0a3199.

* Update wording for error message

Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>

* Update weakref error message, too

* Refactor constant to a pyversions module

* Fix compiler errors

* Another wording update

Co-authored-by: David Hewitt <mail@davidhewitt.dev>

* And make weakref wording the same

* Fix compiler error due to using weakref in our own code

* Fix after merge

* apply conditional pyclass

* update conditional compilation in tests

---------

Co-authored-by: cojmeister <luqas.c@gmail.com>
Co-authored-by: David Hewitt <mail@davidhewitt.dev>
Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>
2024-06-16 10:23:03 +00:00
David Hewitt 9648d595a5
implement `PartialEq<str>` for `Bound<'py, PyString>` (#4245)
* implement `PartialEq<str>` for `Bound<'py, PyString>`

* fixup conditional code

* document equality semantics for `Bound<'_, PyString>`

* fix doc example
2024-06-16 08:19:21 +00:00
David Hewitt 0b2f19b3c9
fix `__dict__` on Python 3.9 with limited API (#4251)
* fix `__dict__` on Python 3.9 with limited API

* [review] Icxolu suggestions

Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>

* [review] Icxolu

* missing import

---------

Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>
2024-06-16 07:57:44 +00:00
David Hewitt 591cdb0bf8
implement `PyModuleMethods::filename` on PyPy (#4249) 2024-06-14 19:08:35 +00:00
Icxolu 5749a08b63
ci: updates for Rust 1.79 (#4244)
* ci: updates for Rust 1.79

* ci: fix beta clippy

* ci: fix `dead_code` warning on nightly
2024-06-13 18:24:13 +00:00
David Hewitt f66124a79b
pypy/graalpy: set `Py_LIMITED_API` when `abi3` requested (#4237)
* pypy/graalpy: set `Py_LIMITED_API` when `abi3` requested

* add newsfragment
2024-06-10 07:26:05 +00:00
JRRudy1 d2dca2169c
Added `as_super` methods to `PyRef` and `PyRefMut`. (#4219)
* Added `PyRef::as_super` and `PyRefMut::as_super` methods, including docstrings and tests. The implementation of these methods also required adding `#[repr(transparent)]` to the `PyRef` and `PyRefMut` structs.

* Added newsfragment entry.

* Changed the `AsRef<U>`/`AsMut<U>` impls for `PyRef` and `PyRefMut` to use the new `as_super` methods. Added the `PyRefMut::downgrade` associated function for converting `&PyRefMut` to `&PyRef`. Updated tests and docstrings to better demonstrate the new functionality.

* Fixed newsfragment filename.

* Removed unnecessary re-borrows flagged by clippy.

* retrigger checks

* Updated `PyRef::as_super`, `PyRefMut::as_super`, and `PyRefMut::downgrade` to use `.cast()` instead of `as _` pointer casts. Fixed typo.

* Updated `PyRef::as_super` and `PyRefMut::downgrade` to use `ptr_from_ref` for the initial cast to `*const _` instead of `as _` casts.

* Added `pyo3::internal_tricks::ptr_from_mut` function alongside the `ptr_from_ref` added in PR #4240. Updated `PyRefMut::as_super` to use this method instead of `as *mut _`.

* Updated the user guide to recommend `as_super` for accessing the base class instead of `as_ref`, and updated the subsequent example/doctest to demonstrate this functionality.

* Improved tests for the `as_super` methods.

* Updated newsfragment to include additional changes.

* Fixed formatting.

---------

Co-authored-by: jrudolph <jrudolph@anl.gov>
2024-06-09 07:17:23 +00:00
David Hewitt 9c67057745
refactor: use `ptr_from_ref` and ptr `.cast()` (#4240)
* refactor: use `ptr_from_ref` and ptr `.cast()`

* fix unused imports
2024-06-07 22:47:27 +00:00
Michael Gilbert b8fb367582
feat: Add 'ord' option for PyClass and corresponding tests (#4202)
* feat: Add 'ord' option for PyClass and corresponding tests

Updated the macros back-end to include 'ord' as an option for PyClass allowing for Python-style ordering comparison of enum variants. Additionally, test cases to verify the proper functioning of this new feature have been introduced.

* update: fix formatting with cargo fmt

* update: documented added feature in newsfragments

* update: updated saved errors for comparison test for invalid pyclass args

* update: removed nested match arms and extended cases for ordering instead

* update: alphabetically ordered entries

* update: added section to class documentation with example for using ord argument.

* refactor: reduced duplication of code using closure to process tokens.

* update: used ensure_spanned macro to emit compile time errors for uses of ord on complex enums or structs, updated test errors for bad compile cases

* fix: remove errant character

* update: added note about PartialOrd being required.

* feat: implemented ordering for structs and complex enums.  Retained the equality logic for simple enums until PartialEq is deprecated.

* update: adjusted compile time error checks for missing PartialOrd implementations.  Refactored growing set of comparison tests for simple and complex enums and structs into separate test file.

* fix: updated with clippy findings

* update: added not to pyclass parameters on ord (assumes that eq will be implemented and merged first)

* update: rebased on main after merging of `eq` feature

* update: format update

* update: update all test output and doc tests

* Update guide/src/class.md

Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>

* Update pyo3-macros-backend/src/pyclass.rs

Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>

* Update newsfragments/4202.added.md

Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>

* Update guide/pyclass-parameters.md

Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>

* update: added note about `ord` implementation with example.

* fix doc formatting

---------

Co-authored-by: Michael Gilbert <git.3mc1o@aleeas.com>
Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>
2024-06-07 19:08:53 +00:00
David Hewitt fbb6f20c2b
migration: close all 0.20->0.21 items (#4238) 2024-06-07 14:26:36 +00:00
Thomas Tanon 74619143b6
Declarative modules: make sure to emit doc comments and other attributes (#4236)
* Declarative modules: make sure to emmit doc comments and other attributes

* Adds a test

* Apply suggestions from code review

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
2024-06-06 21:19:37 +00:00
Adam Reichold c644c0b0b8
Lazy-initialize the global reference pool to reduce its overhead when unused (#4178)
* Add benchmarks exercising the global reference count decrement pool.

* Lazy-initialize the global reference pool to reduce its overhead when unused
2024-06-06 08:45:16 +00:00
Thomas Tanon 11d67b3acc
Automated module= field creation in declarative modules (#4213)
* Automated module= field creation in declarative modules

Sets automatically the "module" field of all contained classes and submodules in a declarative module

Adds the "module" field to pymodule attributes in order to set the name of the parent modules. By default, the module is assumed to be a root module

* fix guide test error

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
2024-06-06 07:54:26 +00:00
Cheuk Ting Ho 37a5f6a94e
remove internal APIs from pyo3-ffi (#4201)
* remove internal APIs from pyo3-ffi

* fix formating

* add conditional import

* remove _Py_c_neg/abs/pow

* fix formating

* adding changelog

* expose PyAnyMethods::neg/pos/abs and use them

* Update src/types/any.rs

Co-authored-by: David Hewitt <mail@davidhewitt.dev>

* Update src/types/any.rs

Co-authored-by: David Hewitt <mail@davidhewitt.dev>

* Adding details to changelog

* update docs

* remove PyREsultExt import for GraalPy

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
2024-06-05 21:21:44 +00:00
A. Cody Schuffelen 93ef056711
Use `Ident::parse_any` for `name` attributes (#4226)
This makes it possible to use rust keywords as the name of python class
methods and standalone functions. For example:

```
struct MyClass {
}

impl MyClass {
    #[new]
    fn new() -> Self {
        MyClass {}
    }

    #[pyo3(name = "struct")]
    fn struct_method(&self) -> usize {
        42
    }
}

fn struct_function() -> usize {
    42
}
```

From the [`syn::Ident`
documentation](https://docs.rs/syn/2.0.66/syn/struct.Ident.html):

> An identifier constructed with `Ident::new` is permitted to be a Rust
keyword, though parsing one through its
[`Parse`](https://docs.rs/syn/2.0.66/syn/parse/trait.Parse.html)
implementation rejects Rust keywords. Use `input.call(Ident::parse_any)`
when parsing to match the behaviour of `Ident::new`.

Fixes issue #4225
2024-06-03 19:45:36 +00:00
Icxolu 7e5884c40b
fix incorrect `__richcmp__` for `eq_int` only simple enums (#4224)
* fix incorrect `__richcmp__` for `eq_int` only simple enums

* add tests for deprecated simple enum eq behavior

* only emit deprecation warning if neither `eq` nor `eq_int` were given

* require `eq` for `eq_int`
2024-06-03 18:49:36 +00:00
Icxolu 36cdeb29c1
fix incorrect raw identifier handling for the `name` attribute of `pyfunction`s (#4229) 2024-06-03 17:32:35 +00:00
liammcinroy b4b780b475
Allow module= attribute in complex enum variants (#4228)
* Allow module= attribute in complex enum variants

* stderr test update

* towncrier

* inherit `module`, rather than specifying everywhere.

* clippy fix
2024-06-03 07:57:04 +00:00
David Hewitt 88b6f23e3b
fix calling POOL.update_counts() when no `gil-refs` feature (#4200)
* fix calling POOL.update_counts() when no `gil-refs` feature

* fixup conditional compilation

* always increment gil count

* correct test

* clippy fix

* fix clippy

* Deduplicate construction of GILGuard::Assumed.

* Remove unsafe-block-with-unsafe-function triggering errors in our MSRV builds.

---------

Co-authored-by: Adam Reichold <adam.reichold@t-online.de>
2024-06-02 11:11:14 +00:00
JRRudy1 5d47c4ae4c
Added `ToPyObject` and `IntoPy<PyObject>` impls for `PyBackedStr`. (#4205)
* Added `ToPyObject` and `Into<PyObject>` impls for `PyBackedStr`.

* Create 4205.added.md

* Added attributes limiting the `ToPyObject` and `IntoPy<PyObject>` impls to the case where `cfg(any(Py_3_10, not(Py_LIMITED_API)))`. When this cfg does not apply, the conversion is less trivial since the `storage` is actually `PyBytes`, not `PyString`.

* Fixed imports format.

* Updated the `ToPyObject` and `IntoPy<PyObject>` impls to support the `cfg(not(any(Py_3_10, not(Py_LIMITED_API))))` case by converting the `PyBytes` back to `PyString`.

* Added `ToPyObject` and `IntoPy<PyObject>` impls for `PyBackedBytes`.

* Added tests for the `PyBackedBytes` conversion impls.

* Updated newsfragment entry to include the `PyBackedBytes` impls.

* Changed the `IntoPy` and `ToPyObject` impls for `PyBackedBytes` to produce `PyBytes` regardless of the backing variant. Updated tests to demonstrate this.

* retrigger checks

* Updated `PyBackedStr` conversion tests to extract the result as a `PyBackedStr` instead of `&str` since the latter is not supported under some `cfg`'s.

* Fixed `IntoPy<PyObject> for PyBackedBytes` impl to create `bytes` for both storage types as intended. Updated test to properly catch the error.

---------

Co-authored-by: jrudolph <jrudolph@anl.gov>
2024-06-01 21:09:14 +00:00
Icxolu a7a5c10b8a
add pyclass `hash` option (#4206)
* add pyclass `hash` option

* add newsfragment

* require `frozen` option for `hash`

* simplify `hash` without `frozen` error message

Co-authored-by: David Hewitt <mail@davidhewitt.dev>

* require `eq` for `hash`

* prevent manual `__hash__` with `#pyo3(hash)`

* combine error messages

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
2024-06-01 14:20:20 +00:00
Jasper van Brakel 25c1db4767
Add weakref Python types (#3835)
* Add vscode folder to gitignore

* Initial work on PyWeakRef (weakref.ReferenceType)

* Add documentation for PyWeakRef::upgrade

* Add missing docs for PyWeakRef

* Add PyWeakProxy

* Add PyWeakCallableProxy

* Add PyWeakRefMethods::upgrade_exact and prevent unnecessary panicing

* Change PyWeakRefMethods to exclude infeasible errors.

As a result of the checks  made about the objectpointers in PyO3, all
 errors in PyWeakref_GetObject are already caught. Therefor there is no
 need to check for these errors in the non-ffi layer.

* Add towncrier changes

* Update weakref type doctests to use `py.run_bound`

* Fix to adhere to MSRV

* Make weakref tests independent from macros feature

* Change Weakref tests

* Remove forgotten Debug marcos

* Processed .gitignore and PyResultExt feedback

* Change new methods of weakref types to remove dangling pointers

* Change to reflect deprecation of PyErr::value for PyErr::value_bound

* Change Tests so different class name in older python versions is accounted for

* Change formatting

* Make tests ABI3 compatible

* Prevent the use of PyClass in test for weakref under abi3 Python 3.7 and 3.8

* Disable weakref types when targeting PyPy

* Remove needless borrow from CallableProxy test

* Add Borrowed variants of upgrade and upgrade exact to trait

* Added tests for weakref borrow_upgrade methods

* Change PyWeakRefMethods method names to be more consistent

* Change weakref constructors to take PyAny for main target

* Add track_caller to all panicing weakref methods

* Add PyWeakRefMethods::upgrade*_as_unchecked

* Fix PyWeakProxy and PyWeakCallableProxy Documentation

* Replace deprecated wrap_pyfunction with bound equivalent

* Add (Generic) PyWeakref Type

* Reworked Proxy types into one (PyWeakrefProxy)

* Rename PyWeakRef to PyWeakrefReference

* Change PyWeakrefReference to only use type pointer when it exists

* Remove `#[track_caller]` annotations for now

* Make the gil-refs function feature dependent

* Remove unused AsPyPointer import

* Change docs links to PyNone to not include private module

* Fix string based examples

* Change tests to work for Python 3.13

* Fix cargo clippy for Python 3.13

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
2024-05-31 19:13:50 +00:00
Icxolu d1a7cf400a
add pyclass `eq` option (#4210)
* add pyclass `eq` option

* prevent manual impl of `__richcmp__` or `__eq__` with `#[pyclass(eq)]`

* add simple enum `eq_int` option

* rearrange names to fix deprecation warning

* add newsfragment and migration

* update docs

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
2024-05-31 14:13:30 +00:00
David Brochart cb347370ff
Fix typo (#4222) 2024-05-31 09:44:09 +00:00
David Hewitt 4fe5e8c689
ci: turn off gh-pages benchmarks (#4209)
* ci: turn off gh-pages benchmarks

* update benchmark badge
2024-05-28 08:19:50 +00:00
JRRudy1 934c663612
Added `From<Bound<'py, T>>` impl for `PyClassInitializer<T>`. (#4214)
* Added `From<Bound<'py, T>>` impl for PyClassInitializer<T>.

* Added newsfragment entry.

* Added tests for pyclass constructors returning `Py<Self>` and `Bound<Self>`.

* Fixed tests.

* Updated tests to properly cover the new impl.

---------

Co-authored-by: jrudolph <jrudolph@anl.gov>
2024-05-28 01:49:52 +00:00
David Hewitt 388d1760b5
ci: start testing on 3.13-dev (#4184)
* ci: start testing on 3.13-dev

* ffi fixes for 3.13 beta 1

* support 3.13

* move gevent to be binary-only

* adjust for div_ceil

* fixup pytests
2024-05-25 22:41:26 +00:00
Cheuk Ting Ho d21045cbc1
adding new getter for type obj (#4197)
* adding new getter for type obj

* fixing limited api build

* fix formating ssues from clippy

* add changelog info

* Update newsfragments/4197.added.md

Co-authored-by: David Hewitt <mail@davidhewitt.dev>

* Update src/types/typeobject.rs

Co-authored-by: David Hewitt <mail@davidhewitt.dev>

* Update src/types/typeobject.rs

Co-authored-by: David Hewitt <mail@davidhewitt.dev>

* Update src/types/typeobject.rs

Co-authored-by: David Hewitt <mail@davidhewitt.dev>

* Update src/types/typeobject.rs

Co-authored-by: David Hewitt <mail@davidhewitt.dev>

* using uncheck downcast

* fix formating

* move import

* Update src/types/typeobject.rs

Co-authored-by: Matt Hooks <me@matthooks.com>

* Update src/types/typeobject.rs

Co-authored-by: Matt Hooks <me@matthooks.com>

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
Co-authored-by: Matt Hooks <me@matthooks.com>
2024-05-25 22:39:48 +00:00
David Hewitt 2c654b2906
ci: adjust test to avoid type inference (#4199) 2024-05-21 19:27:20 +00:00
Cheuk Ting Ho 81ba9a8cd5
Include import hook in getting-started.md (#4198) 2024-05-21 18:24:06 +00:00
David Hewitt 3e4b3c5c52
docs: attempt to clarify magic methods supported by PyO3 (#4190)
* docs: attempt to clarify magic methods supported by PyO3

* Update guide/src/class/protocols.md

Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>

---------

Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>
2024-05-19 20:13:11 +00:00
Adam Reichold 674708cb4c
Remove OWNED_OBJECTS thread local when GILPool is disabled. (#4193) 2024-05-19 13:40:55 +00:00
David Hewitt ac273a1612
docs: minor updates to pyenv installs (#4189) 2024-05-19 13:39:29 +00:00
Alex Gaynor fe79f54817
feature gate deprecated APIs for `GILPool` (#4181) 2024-05-17 11:31:52 +00:00
Bruno Kolenbrander fff053bde7
Emit a better error for abi3 inheritance (#4185)
* Emit a better error for abi3 inheritance

* Update tests/test_compile_error.rs

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
2024-05-17 10:55:41 +00:00
Alex Gaynor 1c64a03ea0
Move GIL counting from GILPool to GILGuard (#4188) 2024-05-17 04:25:41 +00:00
newcomertv 88f2f6f4d5
feat: support pyclass on tuple enums (#4072)
* feat: support pyclass on tuple enums

* cargo fmt

* changelog

* ruff format

* rebase with adaptation for FnArg refactor

* fix class.md from pr comments

* add enum tuple variant getitem implementation

* fmt

* progress toward getitem and len impl on derive pyclass for complex enum tuple

* working getitem and len slots for complex tuple enum pyclass derivation

* refactor code generation

* address PR concerns
- take py from function argument on get_item
- make more general slot def implementation
- remove unnecessary function arguments
- add testcases for uncovered cases including future feature match_args

* add tracking issue

* fmt

* ruff

* remove me

* support match_args for tuple enum

* integrate FnArg now takes Cow

* fix empty and single element tuples

* use impl_py_slot_def for cimplex tuple enum slots

* reverse erroneous doc change

* Address latest comments

* formatting suggestion

* fix :
- clippy beta
- better compile error (+related doc and test)

---------

Co-authored-by: Chris Arderne <chris@translucent.app>
2024-05-17 02:59:00 +00:00
Alex Gaynor 8de1787580
Change `GILGuard` to be able to represent a GIL that was already held (#4187)
See #4181
2024-05-16 21:55:05 +00:00
David Hewitt 7790dab480
emit rustc-check-cfg only on rust 1.80+ (#4168) 2024-05-15 11:11:49 +00:00
Icxolu 10152a7078
feature gate `PyCell` (#4177)
* feature gate `PyCell`

* feature gate `HasPyGilRef` completely

* bump version
2024-05-12 18:30:08 +00:00
Alex Gaynor 57500d9b09
Updates comments regarding the reference pool that were inaccurate (#4176) 2024-05-11 16:48:38 +00:00
Adam Reichold c5f9001985
Remove deferred reference count increments and make the global reference pool optional (#4095)
* Add feature controlling the global reference pool to enable avoiding its overhead.

* Document reference-pool feature in the performance guide.

* Invert semantics of feature to disable reference pool so the new behaviour becomes opt-in

* Remove delayed reference count increments as we cannot prevent reference count errors as long as these are available

* Adjust tests to be compatible with disable-reference-pool feature

* Adjust tests to be compatible with py-clone feature

* Adjust the GIL benchmark to the updated reference pool semantics.

* Further extend and clarify the documentation of the py-clone and disable-reference-pool features

* Replace disable-reference-pool feature by pyo3_disable_reference_pool conditional compilation flag

Such a flag is harder to use and thereby also harder to abuse. This seems
appropriate as this is purely a performance-oriented change which show only be
enabled by leaf crates and brings with it additional highly implicit sources of
process aborts.

* Add pyo3_leak_on_drop_without_reference_pool to turn aborts into leaks when the global reference pool is disabled and the GIL is not held
2024-05-11 14:48:45 +00:00
Icxolu 033caa8fd1
split more impl blocks (#4175) 2024-05-11 13:48:17 +00:00
Icxolu 444be3bafa
feature gate deprecated APIs for `Python` (#4173) 2024-05-10 18:28:30 +00:00
Icxolu 1e8e09dce3
feature gate `as/into_gil_ref` APIs (Part 3) (#4172) 2024-05-10 17:03:57 +00:00
Icxolu aef0a05719
deprecate implicit default for trailing optional arguments (#4078)
* deprecate "trailing optional arguments" implicit default behaviour

* add newsfragment

* generate individual deprecation messages per function

* add migration guide entry
2024-05-10 10:34:58 +00:00
Alex Gaynor 104328ce14
feature gate deprecated more APIs for `Py` (#4169) 2024-05-10 05:54:08 +00:00
David Hewitt f3c7b90def
remove function pointer wrappers no longer needed for MSRV (#4167) 2024-05-09 22:22:17 +00:00
Icxolu 21c02484d0
feature gate APIs using `into_gil_ref` (Part 2) (#4166) 2024-05-09 22:21:48 +00:00
Icxolu 7beb64a8ca
allow constructor customization of complex enum variants (#4158)
* allow `#[pyo3(signature = ...)]` on complex enum variants to specify constructor signature

* rename keyword to `constructor`

* review feedback

* add docs in guide

* add newsfragment
2024-05-09 21:08:23 +00:00
David Matos 2d19b7e2a7
Add `num-rational` support for Python's `fractions.Fraction` type (#4148)
* Add `num-rational` support for Python's `fractions.Fraction` type

* Add newsfragment

* Use Bound instead

* Handle objs which atts are incorrect

* Add extra test

* Add tests for wasm32 arch

* add type for wasm32 clipppy
2024-05-09 15:37:53 +00:00
Icxolu 635cb8075c
feature gate APIs using `into_gil_ref` (Part 1) (#4160) 2024-05-09 07:58:44 +00:00
Icxolu d803f7f8df
store the `FnArg` ident as a `Cow` instead of a reference (#4157)
This allow also storing idents that were generated
as part of the macro instead of only ones the were
present in the source code. This is needed for
example in complex enum tuple variants.
2024-05-08 08:04:42 +00:00
Icxolu 72be1cddba
emit `cargo:rustc-check-cfg=CHECK_CFG` for `pyo3`s config names (#4163) 2024-05-08 05:46:00 +00:00
Icxolu 7263fa92ef
feature gate deprecated APIs for `PyBool` (#4159) 2024-05-04 17:45:27 +00:00
deedy5 ef13bc66e9
Add `pyreqwest_impersonate` to example projects (#4123) 2024-05-04 07:48:15 +00:00
Icxolu e835ff0ec3
handle `#[pyo3(from_py_with = ...)]` on dunder (`__magic__`) methods (#4117)
* handle `#[pyo3(from_py_with = ...)]` on dunder (__magic__) methods

* add newsfragment
2024-05-04 07:39:40 +00:00
Heran Lin c10c7429d8
docs: Remove out-dated information for pyenv (#4138) 2024-05-04 07:32:27 +00:00
Alex Gaynor d1a0c7278f
feature gate deprecated APIs for `PyCFunction` (#4154) 2024-05-03 19:50:38 +00:00
Icxolu c08f6c77a6
feature gate deprecated APIs for `marshal` (#4149) 2024-05-03 18:15:25 +00:00
Alex Gaynor f3ab62cb7e
feature gate deprecated APIs for `PyModule` (#4151) 2024-05-03 17:10:49 +00:00
Alex Gaynor 93cfb51ebb
feature gate deprecated APIs for `PyMemoryView` (#4152) 2024-05-03 16:02:19 +00:00
Icxolu 7cbb85476c
fix `check-guide` ci workflow (#4146) 2024-05-03 10:17:14 +00:00
Icxolu cd3f3ed67c
ci: updates for Rust 1.78 (#4150)
* ci: updates for Rust 1.78

* ci: fix clippy

* restrict `invalid_pymethods_duplicates` to unlimited api with `full`
2024-05-03 07:42:30 +00:00
Icxolu 9a808c35c6
fix `clippy-beta` ci workflow (#4147) 2024-05-01 19:05:51 +00:00
Icxolu a454f6e9cc
feature gate deprecated APIs for `PyFloat` and `PyComplex` (#4145) 2024-05-01 17:13:49 +00:00
Alex Gaynor 5534a7bee8
feature gate deprecated APIs for `PyBuffer` (#4144) 2024-05-01 12:18:12 +00:00
Icxolu dc9a41521a
feature gate deprecated APIs for `Py` (#4142) 2024-05-01 10:57:03 +00:00
Alex Gaynor 261d27d197
feature gate deprecated APIs for `PySlice` (#4141) 2024-04-30 23:55:43 +00:00
Icxolu 2f3a33fda1
feature gate deprecated APIs for `PyList` (#4127) 2024-04-30 22:00:31 +00:00
Icxolu 82c00a2fe4
port `PyAny` tests to `Bound` API (#4140) 2024-04-30 21:49:00 +00:00
Icxolu 4616838ee1
port `PySequence` tests to `Bound` API (#4139) 2024-04-30 18:53:40 +00:00
Icxolu 22c5cff039
feature gate deprecated APIs for `PyErr` and exceptions (#4136) 2024-04-30 16:58:53 +00:00
Icxolu d5452bcd8d
feature gate deprecated APIs for `PyType`, `PyTypeInfo` and `PySuper` (#4134) 2024-04-28 21:03:51 +00:00
Alex Gaynor 9e1960ea34
Update MSRV to 1.63 (#4129)
* Bump MSRV to 1.63

* Drop parking_lot in favor of std::sync

* Make portable-atomic dep conditional

* Remove no longer required cfg
2024-04-28 16:11:28 +00:00
Icxolu 6fb972b232
feature gate deprecated APIs for `PyEllipsis`, `PyNone` and `PyNotImplemented` (#4132) 2024-04-27 15:34:13 +00:00
Icxolu 059c8b3862
feature gate deprecated APIs for `PyBytes` and `PyPyByteArray` (#4131) 2024-04-27 11:34:27 +00:00
Alex Gaynor 8ff5e5b0ab
Fix lychee for guide (#4130)
* Fix lychee for guide

* Update nightly in netlify
2024-04-27 05:12:11 +00:00
David Matos c66ed292ec
Disable PyUnicode_DATA on PyPy (#4116)
* Disable PYUNICODE_DATA on PyPy

* Add newsfragment

* Adjust import on PyString
2024-04-26 06:00:13 +00:00
Icxolu 8734b76f60
feature gate deprecated APIs for `PyIterator` (#4119) 2024-04-25 17:44:42 +00:00
Alexander Clausen 3cb286e0d2
docs: fix typo in trait-bounds.md (#4124) 2024-04-25 15:36:29 +00:00
Icxolu 6c2e6f8bcc
feature gate deprecated APIs for `PyFrozenSet` (#4118) 2024-04-24 21:13:27 +00:00
Icxolu c951bf86de
feature gate deprecated APIs for `PyCapsule` (#4112) 2024-04-24 17:34:19 +00:00
Icxolu 013a4476ea
feature gate deprecated APIs for `datetime` types (#4111) 2024-04-24 17:33:53 +00:00
Bruno Kolenbrander f5fee94afc
Scope macro imports more tightly (#4088) 2024-04-23 18:01:41 +00:00
Icxolu 5d2f5b5702
feature gate deprecated APIs for `PyDict` (#4108) 2024-04-23 05:48:27 +00:00
Georg Brandl c2ac9a98e2
fix vectorcall argument handling (#4104)
Fixes #4093

- Make PY_VECTORCALL_ARGUMENTS_OFFSET a size_t like on CPython
- Remove the assert in PyVectorcall_NARGS and use checked cast
2024-04-22 08:56:39 +00:00
Icxolu da2d1aa8b4
feature gate deprecated APIs for `PyString` (#4101) 2024-04-22 07:20:32 +00:00
Icxolu b0ad1e10aa
feature gate deprecated APIs for `PyTuple` (#4107) 2024-04-22 07:19:01 +00:00
David Hewitt 947b372dcc
change `PyAnyMethods::dir` to be fallible (#4100) 2024-04-19 19:35:52 +00:00
David Hewitt cd28e1408e
add `#[track_caller]` to all `Py`/`Bound`/`Borrowed` methods which panic (#4098) 2024-04-19 11:44:36 +00:00
Icxolu d42c00d21d
feature gate deprecated APIs for `PySet` (#4096) 2024-04-19 07:24:26 +00:00
dependabot[bot] b11174e96d
Update heck requirement from 0.4 to 0.5 (#3966)
* Update heck requirement from 0.4 to 0.5

---
updated-dependencies:
- dependency-name: heck
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* newsfragment

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: David Hewitt <mail@davidhewitt.dev>
2024-04-18 10:22:34 +00:00
David Hewitt 2c205d4586
release notes for 0.21.2 (#4091) 2024-04-18 08:59:02 +00:00
dependabot[bot] e64eb72903
build(deps): update chrono-tz requirement from >= 0.6, < 0.9 to >= 0.6, < 0.10 (#4061)
* build(deps): update chrono-tz requirement from >= 0.6, < 0.9 to >= 0.6, < 0.10

Updates the requirements on [chrono-tz](https://github.com/chronotope/chrono-tz) to permit the latest version.
- [Release notes](https://github.com/chronotope/chrono-tz/releases)
- [Changelog](https://github.com/chronotope/chrono-tz/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono-tz/compare/v0.6.0...v0.9.0)

---
updated-dependencies:
- dependency-name: chrono-tz
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* newsfragment

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: David Hewitt <mail@davidhewitt.dev>
2024-04-18 08:58:51 +00:00
Jacob Zhong 03c50a1839
Change the types of `PySliceIndices` and `PySlice::indices (#3761)
* Change the type of `PySliceIndices::slicelength` and `PySlice::indices()`

* Fix example

* Fix fmt
2024-04-18 07:33:07 +00:00
Bruno Kolenbrander 9761abf3a5
Specify higher target-lexicon version (#4087) 2024-04-17 01:07:11 +00:00
Icxolu 03f59eaf45
fix declarative module compile error with `create_exception!` (#4086)
* fix declarative module compile error with `create_exception!`

* add newsfragment
2024-04-16 20:12:18 +00:00
David Matos 2ad2a3f208
docs: Make contributing.md slightly more clear for newer contributors (#4080)
* docs: Make contributing.md slightly more clear for newer contributors

* Remove accidental backticks

Rearrange overview of commands

* Placed on wrong line

* Add extra overview command
2024-04-16 08:17:41 +00:00
David Hewitt a5201c04af
Deprecate the `PySet::empty` gil-ref constructor (#4082)
* Deprecate the `PySet::empty` gil-ref constructor

* add newsfragment
2024-04-14 21:38:40 +00:00
David Matos 8ed5c17b93
Allow use of scientific notation on rust decimal (#4079)
* Allow use of scientific notation on rust decimal

* Add newsfragment

* Pull out var

* Fix clippy warning

* Modify let bindings location
2024-04-14 20:07:17 +00:00
Icxolu cc7e16f4d6
Refactoring of `FnArg` (#4033)
* refactor `FnArg`

* add UI tests

* use enum variant types

* add comment

* remove dead code

* remove last FIXME

* review feedback davidhewitt
2024-04-14 14:19:57 +00:00
Adam Reichold 721100a9e9
Suppress non_local_definitions lint as we often want the non-local effects in macro code (#4074)
* Resolve references to legacy numerical constants and use the associated constants instead

* Suppress non_local_definitions lint as we often want the non-local effects in macro code
2024-04-13 12:59:44 +00:00
Adam Reichold 4e5167db42
Extend guide on interaction between method receivers and lifetime elision. (#4069) 2024-04-13 07:57:13 +00:00
messense 30348b4d3f
Link libpython for AIX target (#4073) 2024-04-13 07:43:06 +00:00
Icxolu ee5216f406
fix declarative-modules compile error (#4054)
* fix declarative-modules compile error

* add newsfragment
2024-04-12 06:34:27 +00:00
Icxolu c8b59d7117
add `#[doc(hidden)]` to the Rust module created by `#[pymodule]` (#4067)
* add `#[doc(hidden)]` to the Rust module created by `#[pymodule]`

* add newsfragment
2024-04-12 06:34:08 +00:00
Liam 9a6b1962d3
Minor: Fix a typo in Contributing.md (#4066) 2024-04-11 21:11:51 +00:00
dependabot[bot] 631c25f2f9
build(deps): bump peaceiris/actions-gh-pages from 3 to 4 (#4062)
Bumps [peaceiris/actions-gh-pages](https://github.com/peaceiris/actions-gh-pages) from 3 to 4.
- [Release notes](https://github.com/peaceiris/actions-gh-pages/releases)
- [Changelog](https://github.com/peaceiris/actions-gh-pages/blob/main/CHANGELOG.md)
- [Commits](https://github.com/peaceiris/actions-gh-pages/compare/v3...v4)

---
updated-dependencies:
- dependency-name: peaceiris/actions-gh-pages
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-11 21:08:44 +00:00
Jeong, Heon 47f9ef4174
Fix typo (#4052)
Fix incorrect closing brackets
2024-04-11 21:07:56 +00:00
Joseph Perez 2f0869a6d6
fix: allow impl of Ungil for deprecated PyCell in nightly (#4053) 2024-04-06 22:36:52 +00:00
Icxolu 7a00b4d357
add descriptive error msg for `__traverse__` receivers other than `&self` (#4045)
* add descriptive error msg for `__traverse__` receivers other than `self`

* add newsfragment

* improve error message
2024-04-04 19:08:51 +00:00
David Hewitt a4aea230ed
fix compile error for multiple async method arguments (#4035) 2024-04-02 17:43:51 +00:00
296 changed files with 14923 additions and 5749 deletions

View File

@ -8,6 +8,6 @@ Please consider adding the following to your pull request:
- docs to all new functions and / or detail in the guide
- tests for all new or changed functions
PyO3's CI pipeline will check your pull request. To run its tests
PyO3's CI pipeline will check your pull request, thus make sure you have checked the `Contributing.md` guidelines. To run most of its tests
locally, you can run ```nox```. See ```nox --list-sessions```
for a list of supported actions.

View File

@ -16,7 +16,7 @@ on:
rust-target:
required: true
type: string
extra-features:
MSRV:
required: true
type: string
@ -54,14 +54,18 @@ jobs:
name: Prepare LD_LIBRARY_PATH (Ubuntu only)
run: echo LD_LIBRARY_PATH=${pythonLocation}/lib >> $GITHUB_ENV
- if: inputs.rust == '1.56.0'
name: Prepare minimal package versions (MSRV only)
run: nox -s set-minimal-package-versions
- if: inputs.rust == inputs.MSRV
name: Prepare MSRV package versions
run: nox -s set-msrv-package-versions
- if: inputs.rust != 'stable'
name: Ignore changed error messages when using trybuild
run: echo "TRYBUILD=overwrite" >> "$GITHUB_ENV"
- if: inputs.rust == 'nightly'
name: Prepare to test on nightly rust
run: echo "MAYBE_NIGHTLY=nightly" >> "$GITHUB_ENV"
- name: Build docs
run: nox -s docs
@ -88,26 +92,31 @@ jobs:
- name: Build (all additive features)
if: ${{ !startsWith(inputs.python-version, 'graalpy') }}
run: cargo build --lib --tests --no-default-features --features "full ${{ inputs.extra-features }}"
run: cargo build --lib --tests --no-default-features --features "multiple-pymethods full $MAYBE_NIGHTLY"
- if: ${{ startsWith(inputs.python-version, 'pypy') }}
name: Build PyPy (abi3-py37)
run: cargo build --lib --tests --no-default-features --features "abi3-py37 full ${{ inputs.extra-features }}"
run: cargo build --lib --tests --no-default-features --features "multiple-pymethods abi3-py37 full $MAYBE_NIGHTLY"
# Run tests (except on PyPy, because no embedding API).
- if: ${{ !startsWith(inputs.python-version, 'pypy') && !startsWith(inputs.python-version, 'graalpy') }}
name: Test
run: cargo test --no-default-features --features "full ${{ inputs.extra-features }}"
run: cargo test --no-default-features --features "full $MAYBE_NIGHTLY"
# Repeat, with multiple-pymethods feature enabled (it's not entirely additive)
- if: ${{ !startsWith(inputs.python-version, 'pypy') && !startsWith(inputs.python-version, 'graalpy') }}
name: Test
run: cargo test --no-default-features --features "multiple-pymethods full $MAYBE_NIGHTLY"
# Run tests again, but in abi3 mode
- if: ${{ !startsWith(inputs.python-version, 'pypy') && !startsWith(inputs.python-version, 'graalpy') }}
name: Test (abi3)
run: cargo test --no-default-features --features "abi3 full ${{ inputs.extra-features }}"
run: cargo test --no-default-features --features "multiple-pymethods abi3 full $MAYBE_NIGHTLY"
# Run tests again, for abi3-py37 (the minimal Python version)
- if: ${{ (!startsWith(inputs.python-version, 'pypy') && !startsWith(inputs.python-version, 'graalpy')) && (inputs.python-version != '3.7') }}
name: Test (abi3-py37)
run: cargo test --no-default-features --features "abi3-py37 full ${{ inputs.extra-features }}"
run: cargo test --no-default-features --features "multiple-pymethods abi3-py37 full $MAYBE_NIGHTLY"
- name: Test proc-macro code
run: cargo test --manifest-path=pyo3-macros-backend/Cargo.toml

View File

@ -31,6 +31,18 @@ jobs:
- name: Check rust formatting (rustfmt)
run: nox -s rustfmt
resolve:
runs-on: ubuntu-latest
outputs:
MSRV: ${{ steps.resolve-msrv.outputs.MSRV }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- name: resolve MSRV
id: resolve-msrv
run:
echo MSRV=`python -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["package"]["rust-version"])'` >> $GITHUB_OUTPUT
semver-checks:
if: github.ref != 'refs/heads/main'
needs: [fmt]
@ -41,13 +53,13 @@ jobs:
- uses: obi1kenobi/cargo-semver-checks-action@v2
check-msrv:
needs: [fmt]
needs: [fmt, resolve]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.56.0
toolchain: ${{ needs.resolve.outputs.MSRV }}
targets: x86_64-unknown-linux-gnu
components: rust-src
- uses: actions/setup-python@v5
@ -57,9 +69,11 @@ jobs:
with:
save-if: ${{ github.event_name != 'merge_group' }}
- run: python -m pip install --upgrade pip && pip install nox
- name: Prepare minimal package versions
run: nox -s set-minimal-package-versions
- run: nox -s check-all
# This is a smoke test to confirm that CI will run on MSRV (including dev dependencies)
- name: Check with MSRV package versions
run: |
nox -s set-msrv-package-versions
nox -s check-all
env:
CARGO_BUILD_TARGET: x86_64-unknown-linux-gnu
@ -141,7 +155,7 @@ jobs:
build-pr:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-build-full') && github.event_name == 'pull_request' }}
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} rust-${{ matrix.rust }}
needs: [fmt]
needs: [fmt, resolve]
uses: ./.github/workflows/build.yml
with:
os: ${{ matrix.platform.os }}
@ -149,13 +163,12 @@ jobs:
python-architecture: ${{ matrix.platform.python-architecture }}
rust: ${{ matrix.rust }}
rust-target: ${{ matrix.platform.rust-target }}
extra-features: ${{ matrix.platform.extra-features }}
MSRV: ${{ needs.resolve.outputs.MSRV }}
secrets: inherit
strategy:
# If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
matrix:
extra-features: ["multiple-pymethods"]
rust: [stable]
python-version: ["3.12"]
platform:
@ -197,11 +210,10 @@ jobs:
python-architecture: "x64",
rust-target: "x86_64-unknown-linux-gnu",
}
extra-features: "nightly multiple-pymethods"
build-full:
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} rust-${{ matrix.rust }}
needs: [fmt]
needs: [fmt, resolve]
uses: ./.github/workflows/build.yml
with:
os: ${{ matrix.platform.os }}
@ -209,13 +221,12 @@ jobs:
python-architecture: ${{ matrix.platform.python-architecture }}
rust: ${{ matrix.rust }}
rust-target: ${{ matrix.platform.rust-target }}
extra-features: ${{ matrix.platform.extra-features }}
MSRV: ${{ needs.resolve.outputs.MSRV }}
secrets: inherit
strategy:
# If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
matrix:
extra-features: ["multiple-pymethods"] # Because MSRV doesn't support this
rust: [stable]
python-version: [
"3.7",
@ -224,6 +235,7 @@ jobs:
"3.10",
"3.11",
"3.12",
"3.13-dev",
"pypy3.7",
"pypy3.8",
"pypy3.9",
@ -255,7 +267,7 @@ jobs:
]
include:
# Test minimal supported Rust version
- rust: 1.56.0
- rust: ${{ needs.resolve.outputs.MSRV }}
python-version: "3.12"
platform:
{
@ -263,7 +275,6 @@ jobs:
python-architecture: "x64",
rust-target: "x86_64-unknown-linux-gnu",
}
extra-features: ""
# Test the `nightly` feature
- rust: nightly
@ -274,7 +285,6 @@ jobs:
python-architecture: "x64",
rust-target: "x86_64-unknown-linux-gnu",
}
extra-features: "nightly multiple-pymethods"
# Run rust beta to help catch toolchain regressions
- rust: beta
@ -285,7 +295,6 @@ jobs:
python-architecture: "x64",
rust-target: "x86_64-unknown-linux-gnu",
}
extra-features: "multiple-pymethods"
# Test 32-bit Windows only with the latest Python version
- rust: stable
@ -296,7 +305,6 @@ jobs:
python-architecture: "x86",
rust-target: "i686-pc-windows-msvc",
}
extra-features: "multiple-pymethods"
# test arm macos runner with the latest Python version
# NB: if the full matrix switchess to arm, switch to x86_64 here
@ -308,7 +316,6 @@ jobs:
python-architecture: "arm64",
rust-target: "aarch64-apple-darwin",
}
extra-features: "multiple-pymethods"
valgrind:
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
@ -496,21 +503,31 @@ jobs:
- run: python3 -m nox -s test-version-limits
check-feature-powerset:
needs: [fmt]
needs: [fmt, resolve]
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
runs-on: ubuntu-latest
name: check-feature-powerset ${{ matrix.rust }}
strategy:
# run on stable and MSRV to check that all combinations of features are expected to build fine on our supported
# range of compilers
matrix:
rust: ["stable"]
include:
- rust: ${{ needs.resolve.outputs.MSRV }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.event_name != 'merge_group' }}
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@master
with:
components: rust-src
- uses: taiki-e/install-action@cargo-hack
toolchain: stable
- uses: taiki-e/install-action@v2
with:
tool: cargo-hack,cargo-minimal-versions
- run: python3 -m pip install --upgrade pip && pip install nox
- run: python3 -m nox -s check-feature-powerset
- run: python3 -m nox -s check-feature-powerset -- ${{ matrix.rust != 'stable' && 'minimal-versions' || '' }}
test-cross-compilation:
needs: [fmt]

View File

@ -22,7 +22,7 @@ jobs:
tag_name: ${{ steps.prepare_tag.outputs.tag_name }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: dtolnay/rust-toolchain@nightly
- name: Setup mdBook
@ -46,94 +46,9 @@ jobs:
- name: Deploy docs and the guide
if: ${{ github.event_name == 'release' }}
uses: peaceiris/actions-gh-pages@v3
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./target/guide/
destination_dir: ${{ steps.prepare_tag.outputs.tag_name }}
full_commit_message: "Upload documentation for ${{ steps.prepare_tag.outputs.tag_name }}"
cargo-benchmark:
if: ${{ github.ref_name == 'main' }}
name: Cargo benchmark
needs: guide-build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: dtolnay/rust-toolchain@stable
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-bench-${{ hashFiles('**/Cargo.toml') }}
continue-on-error: true
- name: Run benchmarks
run: |
python -m pip install --upgrade pip && pip install nox
for bench in pyo3-benches/benches/*.rs; do
bench_name=$(basename "$bench" .rs)
nox -s bench -- --bench "$bench_name" -- --output-format bencher | tee -a output.txt
done
# Download previous benchmark result from cache (if exists)
- name: Download previous benchmark data
uses: actions/cache@v4
with:
path: ./cache
key: ${{ runner.os }}-benchmark
# Run `github-action-benchmark` action
- name: Store benchmark result
uses: benchmark-action/github-action-benchmark@v1
with:
name: pyo3-bench
# What benchmark tool the output.txt came from
tool: "cargo"
# Where the output from the benchmark tool is stored
output-file-path: output.txt
# GitHub API token to make a commit comment
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: ${{ github.event_name != 'pull_request' }}
pytest-benchmark:
if: ${{ github.ref_name == 'main' }}
name: Pytest benchmark
needs: cargo-benchmark
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: dtolnay/rust-toolchain@stable
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-pytest-bench-${{ hashFiles('**/Cargo.toml') }}
continue-on-error: true
- name: Download previous benchmark data
uses: actions/cache@v4
with:
path: ./cache
key: ${{ runner.os }}-pytest-benchmark
- name: Run benchmarks
run: |
python -m pip install --upgrade pip && pip install nox
nox -f pytests/noxfile.py -s bench -- --benchmark-json $(pwd)/output.json
- name: Store benchmark result
uses: benchmark-action/github-action-benchmark@v1
with:
name: pytest-bench
tool: "pytest"
output-file-path: output.json
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: ${{ github.event_name != 'pull_request' }}

View File

@ -2,6 +2,7 @@
set -uex
rustup update nightly
rustup default nightly
PYO3_VERSION=$(cargo search pyo3 --limit 1 | head -1 | tr -s ' ' | cut -d ' ' -f 3 | tr -d '"')

View File

@ -10,6 +10,99 @@ To see unreleased changes, please see the [CHANGELOG on the main branch guide](h
<!-- towncrier release notes start -->
## [0.22.1] - 2024-07-06
### Added
- Add `#[pyo3(submodule)]` option for declarative `#[pymodule]`s. [#4301](https://github.com/PyO3/pyo3/pull/4301)
- Implement `PartialEq<bool>` for `Bound<'py, PyBool>`. [#4305](https://github.com/PyO3/pyo3/pull/4305)
### Fixed
- Return `NotImplemented` instead of raising `TypeError` from generated equality method when comparing different types. [#4287](https://github.com/PyO3/pyo3/pull/4287)
- Handle full-path `#[pyo3::prelude::pymodule]` and similar for `#[pyclass]` and `#[pyfunction]` in declarative modules.[#4288](https://github.com/PyO3/pyo3/pull/4288)
- Fix 128-bit int regression on big-endian platforms with Python <3.13. [#4291](https://github.com/PyO3/pyo3/pull/4291)
- Stop generating code that will never be covered with declarative modules. [#4297](https://github.com/PyO3/pyo3/pull/4297)
- Fix invalid deprecation warning for trailing optional on `#[setter]` function. [#4304](https://github.com/PyO3/pyo3/pull/4304)
## [0.22.0] - 2024-06-24
### Packaging
- Update `heck` dependency to 0.5. [#3966](https://github.com/PyO3/pyo3/pull/3966)
- Extend range of supported versions of `chrono-tz` optional dependency to include version 0.10. [#4061](https://github.com/PyO3/pyo3/pull/4061)
- Update MSRV to 1.63. [#4129](https://github.com/PyO3/pyo3/pull/4129)
- Add optional `num-rational` feature to add conversions with Python's `fractions.Fraction`. [#4148](https://github.com/PyO3/pyo3/pull/4148)
- Support Python 3.13. [#4184](https://github.com/PyO3/pyo3/pull/4184)
### Added
- Add `PyWeakref`, `PyWeakrefReference` and `PyWeakrefProxy`. [#3835](https://github.com/PyO3/pyo3/pull/3835)
- Support `#[pyclass]` on enums that have tuple variants. [#4072](https://github.com/PyO3/pyo3/pull/4072)
- Add support for scientific notation in `Decimal` conversion. [#4079](https://github.com/PyO3/pyo3/pull/4079)
- Add `pyo3_disable_reference_pool` conditional compilation flag to avoid the overhead of the global reference pool at the cost of known limitations as explained in the performance section of the guide. [#4095](https://github.com/PyO3/pyo3/pull/4095)
- Add `#[pyo3(constructor = (...))]` to customize the generated constructors for complex enum variants. [#4158](https://github.com/PyO3/pyo3/pull/4158)
- Add `PyType::module`, which always matches Python `__module__`. [#4196](https://github.com/PyO3/pyo3/pull/4196)
- Add `PyType::fully_qualified_name` which matches the "fully qualified name" defined in [PEP 737](https://peps.python.org/pep-0737). [#4196](https://github.com/PyO3/pyo3/pull/4196)
- Add `PyTypeMethods::mro` and `PyTypeMethods::bases`. [#4197](https://github.com/PyO3/pyo3/pull/4197)
- Add `#[pyclass(ord)]` to implement ordering based on `PartialOrd`. [#4202](https://github.com/PyO3/pyo3/pull/4202)
- Implement `ToPyObject` and `IntoPy<PyObject>` for `PyBackedStr` and `PyBackedBytes`. [#4205](https://github.com/PyO3/pyo3/pull/4205)
- Add `#[pyclass(hash)]` option to implement `__hash__` in terms of the `Hash` implementation [#4206](https://github.com/PyO3/pyo3/pull/4206)
- Add `#[pyclass(eq)]` option to generate `__eq__` based on `PartialEq`, and `#[pyclass(eq_int)]` for simple enums to implement equality based on their discriminants. [#4210](https://github.com/PyO3/pyo3/pull/4210)
- Implement `From<Bound<'py, T>>` for `PyClassInitializer<T>`. [#4214](https://github.com/PyO3/pyo3/pull/4214)
- Add `as_super` methods to `PyRef` and `PyRefMut` for accesing the base class by reference. [#4219](https://github.com/PyO3/pyo3/pull/4219)
- Implement `PartialEq<str>` for `Bound<'py, PyString>`. [#4245](https://github.com/PyO3/pyo3/pull/4245)
- Implement `PyModuleMethods::filename` on PyPy. [#4249](https://github.com/PyO3/pyo3/pull/4249)
- Implement `PartialEq<[u8]>` for `Bound<'py, PyBytes>`. [#4250](https://github.com/PyO3/pyo3/pull/4250)
- Add `pyo3_ffi::c_str` macro to create `&'static CStr` on Rust versions which don't have 1.77's `c""` literals. [#4255](https://github.com/PyO3/pyo3/pull/4255)
- Support `bool` conversion with `numpy` 2.0's `numpy.bool` type [#4258](https://github.com/PyO3/pyo3/pull/4258)
- Add `PyAnyMethods::{bitnot, matmul, floor_div, rem, divmod}`. [#4264](https://github.com/PyO3/pyo3/pull/4264)
### Changed
- Change the type of `PySliceIndices::slicelength` and the `length` parameter of `PySlice::indices()`. [#3761](https://github.com/PyO3/pyo3/pull/3761)
- Deprecate implicit default for trailing optional arguments [#4078](https://github.com/PyO3/pyo3/pull/4078)
- `Clone`ing pointers into the Python heap has been moved behind the `py-clone` feature, as it must panic without the GIL being held as a soundness fix. [#4095](https://github.com/PyO3/pyo3/pull/4095)
- Add `#[track_caller]` to all `Py<T>`, `Bound<'py, T>` and `Borrowed<'a, 'py, T>` methods which can panic. [#4098](https://github.com/PyO3/pyo3/pull/4098)
- Change `PyAnyMethods::dir` to be fallible and return `PyResult<Bound<'py, PyList>>` (and similar for `PyAny::dir`). [#4100](https://github.com/PyO3/pyo3/pull/4100)
- The global reference pool (to track pending reference count decrements) is now initialized lazily to avoid the overhead of taking a mutex upon function entry when the functionality is not actually used. [#4178](https://github.com/PyO3/pyo3/pull/4178)
- Emit error messages when using `weakref` or `dict` when compiling for `abi3` for Python older than 3.9. [#4194](https://github.com/PyO3/pyo3/pull/4194)
- Change `PyType::name` to always match Python `__name__`. [#4196](https://github.com/PyO3/pyo3/pull/4196)
- Remove CPython internal ffi call for complex number including: add, sub, mul, div, neg, abs, pow. Added PyAnyMethods::{abs, pos, neg} [#4201](https://github.com/PyO3/pyo3/pull/4201)
- Deprecate implicit integer comparision for simple enums in favor of `#[pyclass(eq_int)]`. [#4210](https://github.com/PyO3/pyo3/pull/4210)
- Set the `module=` attribute of declarative modules' child `#[pymodule]`s and `#[pyclass]`es. [#4213](https://github.com/PyO3/pyo3/pull/4213)
- Set the `module` option for complex enum variants from the value set on the complex enum `module`. [#4228](https://github.com/PyO3/pyo3/pull/4228)
- Respect the Python "limited API" when building for the `abi3` feature on PyPy or GraalPy. [#4237](https://github.com/PyO3/pyo3/pull/4237)
- Optimize code generated by `#[pyo3(get)]` on `#[pyclass]` fields. [#4254](https://github.com/PyO3/pyo3/pull/4254)
- `PyCFunction::new`, `PyCFunction::new_with_keywords` and `PyCFunction::new_closure` now take `&'static CStr` name and doc arguments (previously was `&'static str`). [#4255](https://github.com/PyO3/pyo3/pull/4255)
- The `experimental-declarative-modules` feature is now stabilized and available by default. [#4257](https://github.com/PyO3/pyo3/pull/4257)
### Fixed
- Fix panic when `PYO3_CROSS_LIB_DIR` is set to a missing path. [#4043](https://github.com/PyO3/pyo3/pull/4043)
- Fix a compile error when exporting an exception created with `create_exception!` living in a different Rust module using the `declarative-module` feature. [#4086](https://github.com/PyO3/pyo3/pull/4086)
- Fix FFI definitions of `PY_VECTORCALL_ARGUMENTS_OFFSET` and `PyVectorcall_NARGS` to fix a false-positive assertion. [#4104](https://github.com/PyO3/pyo3/pull/4104)
- Disable `PyUnicode_DATA` on PyPy: not exposed by PyPy. [#4116](https://github.com/PyO3/pyo3/pull/4116)
- Correctly handle `#[pyo3(from_py_with = ...)]` attribute on dunder (`__magic__`) method arguments instead of silently ignoring it. [#4117](https://github.com/PyO3/pyo3/pull/4117)
- Fix a compile error when declaring a standalone function or class method with a Python name that is a Rust keyword. [#4226](https://github.com/PyO3/pyo3/pull/4226)
- Fix declarative modules discarding doc comments on the `mod` node. [#4236](https://github.com/PyO3/pyo3/pull/4236)
- Fix `__dict__` attribute missing for `#[pyclass(dict)]` instances when building for `abi3` on Python 3.9. [#4251](https://github.com/PyO3/pyo3/pull/4251)
## [0.21.2] - 2024-04-16
### Changed
- Deprecate the `PySet::empty()` gil-ref constructor. [#4082](https://github.com/PyO3/pyo3/pull/4082)
### Fixed
- Fix compile error for `async fn` in `#[pymethods]` with a `&self` receiver and more than one additional argument. [#4035](https://github.com/PyO3/pyo3/pull/4035)
- Improve error message for wrong receiver type in `__traverse__`. [#4045](https://github.com/PyO3/pyo3/pull/4045)
- Fix compile error when exporting a `#[pyclass]` living in a different Rust module using the `experimental-declarative-modules` feature. [#4054](https://github.com/PyO3/pyo3/pull/4054)
- Fix `missing_docs` lint triggering on documented `#[pymodule]` functions. [#4067](https://github.com/PyO3/pyo3/pull/4067)
- Fix undefined symbol errors for extension modules on AIX (by linking `libpython`). [#4073](https://github.com/PyO3/pyo3/pull/4073)
## [0.21.1] - 2024-04-01
### Added
@ -1731,7 +1824,10 @@ Yanked
- Initial release
[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.21.1...HEAD
[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.22.1...HEAD
[0.22.1]: https://github.com/pyo3/pyo3/compare/v0.22.0...v0.22.1
[0.22.0]: https://github.com/pyo3/pyo3/compare/v0.21.2...v0.22.0
[0.21.2]: https://github.com/pyo3/pyo3/compare/v0.21.1...v0.21.2
[0.21.1]: https://github.com/pyo3/pyo3/compare/v0.21.0...v0.21.1
[0.21.0]: https://github.com/pyo3/pyo3/compare/v0.20.3...v0.21.0
[0.21.0-beta.0]: https://github.com/pyo3/pyo3/compare/v0.20.3...v0.21.0-beta.0

View File

@ -1,6 +1,6 @@
[package]
name = "pyo3"
version = "0.21.1"
version = "0.23.0-dev"
description = "Bindings to Python interpreter"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
readme = "README.md"
@ -12,20 +12,19 @@ categories = ["api-bindings", "development-tools::ffi"]
license = "MIT OR Apache-2.0"
exclude = ["/.gitignore", ".cargo/config", "/codecov.yml", "/Makefile", "/pyproject.toml", "/noxfile.py", "/.github", "/tests/test_compile_error.rs", "/tests/ui"]
edition = "2021"
rust-version = "1.56"
rust-version = "1.63"
[dependencies]
cfg-if = "1.0"
libc = "0.2.62"
parking_lot = ">= 0.11, < 0.13"
memoffset = "0.9"
portable-atomic = "1.0"
once_cell = "1.13"
# ffi bindings to the python interpreter, split into a separate crate so they can be used independently
pyo3-ffi = { path = "pyo3-ffi", version = "=0.21.1" }
pyo3-ffi = { path = "pyo3-ffi", version = "=0.23.0-dev" }
# support crates for macros feature
pyo3-macros = { path = "pyo3-macros", version = "=0.21.1", optional = true }
pyo3-macros = { path = "pyo3-macros", version = "=0.23.0-dev", optional = true }
indoc = { version = "2.0.1", optional = true }
unindent = { version = "0.2.1", optional = true }
@ -33,23 +32,27 @@ unindent = { version = "0.2.1", optional = true }
inventory = { version = "0.3.0", optional = true }
# crate integrations that can be added using the eponymous features
anyhow = { version = "1.0", optional = true }
anyhow = { version = "1.0.1", optional = true }
chrono = { version = "0.4.25", default-features = false, optional = true }
chrono-tz = { version = ">= 0.6, < 0.9", default-features = false, optional = true }
chrono-tz = { version = ">= 0.6, < 0.10", 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 }
num-bigint = { version = "0.4", optional = true }
num-bigint = { version = "0.4.2", optional = true }
num-complex = { version = ">= 0.2, < 0.5", optional = true }
rust_decimal = { version = "1.0.0", default-features = false, optional = true }
num-rational = {version = "0.4.1", optional = true }
rust_decimal = { version = "1.15", default-features = false, optional = true }
serde = { version = "1.0", optional = true }
smallvec = { version = "1.0", optional = true }
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
portable-atomic = "1.0"
[dev-dependencies]
assert_approx_eq = "1.1.0"
chrono = "0.4.25"
chrono-tz = ">= 0.6, < 0.9"
chrono-tz = ">= 0.6, < 0.10"
# Required for "and $N others" normalization
trybuild = ">=1.0.70"
proptest = { version = "1.0", default-features = false, features = ["std"] }
@ -60,7 +63,7 @@ rayon = "1.6.1"
futures = "0.3.28"
[build-dependencies]
pyo3-build-config = { path = "pyo3-build-config", version = "=0.21.1", features = ["resolve-config"] }
pyo3-build-config = { path = "pyo3-build-config", version = "=0.23.0-dev", features = ["resolve-config"] }
[features]
default = ["macros"]
@ -72,9 +75,6 @@ experimental-async = ["macros", "pyo3-macros/experimental-async"]
# and IntoPy traits
experimental-inspect = []
# Enables annotating Rust inline modules with #[pymodule] to build Python modules declaratively
experimental-declarative-modules = ["pyo3-macros/experimental-declarative-modules", "macros"]
# Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc.
macros = ["pyo3-macros", "indoc", "unindent"]
@ -104,7 +104,10 @@ generate-import-lib = ["pyo3-ffi/generate-import-lib"]
auto-initialize = []
# Allows use of the deprecated "GIL Refs" APIs.
gil-refs = []
gil-refs = ["pyo3-macros/gil-refs"]
# Enables `Clone`ing references to Python objects `Py<T>` which panics if the GIL is not held.
py-clone = []
# Optimizes PyObject to Vec conversion and so on.
nightly = []
@ -113,19 +116,20 @@ nightly = []
# 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
# "multiple-pymethods", # Not supported by wasm
"anyhow",
"chrono",
"chrono-tz",
"either",
"experimental-async",
"experimental-declarative-modules",
"experimental-inspect",
"eyre",
"hashbrown",
"indexmap",
"num-bigint",
"num-complex",
"num-rational",
"py-clone",
"rust_decimal",
"serde",
"smallvec",
@ -164,7 +168,7 @@ used_underscore_binding = "warn"
[workspace.lints.rust]
elided_lifetimes_in_paths = "warn"
invalid_doc_attributes = "warn"
rust_2018_idioms = "warn"
rust_2018_idioms = { level = "warn", priority = -1 }
rust_2021_prelude_collisions = "warn"
unused_lifetimes = "warn"

View File

@ -23,10 +23,6 @@ To work and develop PyO3, you need Python & Rust installed on your system.
* [virtualenv](https://virtualenv.pypa.io/en/latest/) can also be used with or without Pyenv to use specific installed Python versions.
* [`nox`][nox] is used to automate many of our CI tasks.
### Caveats
* When using pyenv on macOS, installing a Python version using `--enable-shared` is required to make it work. i.e `env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.7.12`
### Help users identify bugs
The [PyO3 Discord server](https://discord.gg/33kcChzH7f) is very active with users who are new to PyO3, and often completely new to Rust. Helping them debug is a great way to get experience with the PyO3 codebase.
@ -92,9 +88,7 @@ Here are a few things to note when you are writing PRs.
### Continuous Integration
The PyO3 repo uses GitHub Actions. PRs are blocked from merging if CI is not successful.
Formatting, linting and tests are checked for all Rust and Python code. In addition, all warnings in Rust code are disallowed (using `RUSTFLAGS="-D warnings"`).
The PyO3 repo uses GitHub Actions. PRs are blocked from merging if CI is not successful. Formatting, linting and tests are checked for all Rust and Python code. In addition, all warnings in Rust code are disallowed (using `RUSTFLAGS="-D warnings"`).
Tests run with all supported Python versions with the latest stable Rust compiler, as well as for Python 3.9 with the minimum supported Rust version.
@ -103,9 +97,27 @@ If you are adding a new feature, you should add it to the `full` feature in our
You can run these tests yourself with
`nox`. Use `nox -l` to list the full set of subcommands you can run.
#### Linting Python code
`nox -s ruff`
#### Linting Rust code
`nox -s rustfmt`
#### Semver checks
`cargo semver-checks check-release`
#### Clippy
`nox -s clippy-all`
#### Tests
`cargo test --features full`
#### Check all conditional compilation
`nox -s check-feature-powerset`
#### UI Tests
PyO3 uses [`trybuild`][trybuild] to develop UI tests to capture error messages from the Rust compiler for some of the macro functionality.
PyO3 uses [`trybuild`](https://github.com/dtolnay/trybuild) to develop UI tests to capture error messages from the Rust compiler for some of the macro functionality.
Because there are several feature combinations for these UI tests, when updating them all (e.g. for a new Rust compiler version) it may be helpful to use the `update-ui-tests` nox session:
@ -190,7 +202,7 @@ Second, there is a Python-based benchmark contained in the `pytests` subdirector
You can view what code is and isn't covered by PyO3's tests. We aim to have 100% coverage - please check coverage and add tests if you notice a lack of coverage!
- First, ensure the llmv-cov cargo plugin is installed. You may need to run the plugin through cargo once before using it with `nox`.
- First, ensure the llvm-cov cargo plugin is installed. You may need to run the plugin through cargo once before using it with `nox`.
```shell
cargo install cargo-llvm-cov
cargo llvm-cov

View File

@ -1,10 +1,10 @@
# PyO3
[![actions status](https://img.shields.io/github/actions/workflow/status/PyO3/pyo3/ci.yml?branch=main&logo=github&style=)](https://github.com/PyO3/pyo3/actions)
[![benchmark](https://img.shields.io/badge/benchmark-✓-Green?logo=github)](https://pyo3.rs/dev/bench/)
[![benchmark](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/PyO3/pyo3)
[![codecov](https://img.shields.io/codecov/c/gh/PyO3/pyo3?logo=codecov)](https://codecov.io/gh/PyO3/pyo3)
[![crates.io](https://img.shields.io/crates/v/pyo3?logo=rust)](https://crates.io/crates/pyo3)
[![minimum rustc 1.56](https://img.shields.io/badge/rustc-1.56+-blue?logo=rust)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
[![minimum rustc 1.63](https://img.shields.io/badge/rustc-1.63+-blue?logo=rust)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
[![discord server](https://img.shields.io/discord/1209263839632424990?logo=discord)](https://discord.gg/33kcChzH7f)
[![contributing notes](https://img.shields.io/badge/contribute-on%20github-Green?logo=github)](https://github.com/PyO3/pyo3/blob/main/Contributing.md)
@ -18,7 +18,7 @@
PyO3 supports the following software versions:
- Python 3.7 and up (CPython, PyPy, and GraalPy)
- Rust 1.56 and up
- Rust 1.63 and up
You can use PyO3 to write a native Python module in Rust, or to embed Python in a Rust binary. The following sections explain each of these in turn.
@ -68,7 +68,7 @@ name = "string_sum"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.21.1", features = ["extension-module"] }
pyo3 = { version = "0.22.1", 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.21.1"
version = "0.22.1"
features = ["auto-initialize"]
```
@ -176,6 +176,7 @@ about this topic.
- [pythonize](https://github.com/davidhewitt/pythonize) _Serde serializer for converting Rust objects to JSON-compatible Python objects_
- [pyo3-asyncio](https://github.com/awestlake87/pyo3-asyncio) _Utilities for working with Python's Asyncio library and async functions_
- [rustimport](https://github.com/mityax/rustimport) _Directly import Rust files or crates from Python, without manual compilation step. Provides pyo3 integration by default and generates pyo3 binding code automatically._
- [pyo3-arrow](https://crates.io/crates/pyo3-arrow) _Lightweight [Apache Arrow](https://arrow.apache.org/) integration for pyo3._
## Examples
@ -192,6 +193,7 @@ about this topic.
- [fastuuid](https://github.com/thedrow/fastuuid/) _Python bindings to Rust's UUID library._
- [feos](https://github.com/feos-org/feos) _Lightning fast thermodynamic modeling in Rust with fully developed Python interface._
- [forust](https://github.com/jinlow/forust) _A lightweight gradient boosted decision tree library written in Rust._
- [granian](https://github.com/emmett-framework/granian) _A Rust HTTP server for Python applications._
- [greptimedb](https://github.com/GreptimeTeam/greptimedb/tree/main/src/script) _Support [Python scripting](https://docs.greptime.com/user-guide/python-scripts/overview) in the database_
- [haem](https://github.com/BooleanCat/haem) _A Python library for working on Bioinformatics problems._
- [html-py-ever](https://github.com/PyO3/setuptools-rust/tree/main/examples/html-py-ever) _Using [html5ever](https://github.com/servo/html5ever) through [kuchiki](https://github.com/kuchiki-rs/kuchiki) to speed up html parsing and css-selecting._
@ -210,6 +212,7 @@ about this topic.
- [pyheck](https://github.com/kevinheavey/pyheck) _Fast case conversion library, built by wrapping [heck](https://github.com/withoutboats/heck)._
- Quite easy to follow as there's not much code.
- [pyre](https://github.com/Project-Dream-Weaver/pyre-http) _Fast Python HTTP server written in Rust._
- [pyreqwest_impersonate](https://github.com/deedy5/pyreqwest_impersonate) _The fastest python HTTP client that can impersonate web browsers by mimicking their headers and TLS/JA3/JA4/HTTP2 fingerprints._
- [ril-py](https://github.com/Cryptex-github/ril-py) _A performant and high-level image processing library for Python written in Rust._
- [river](https://github.com/online-ml/river) _Online machine learning in python, the computationally heavy statistics algorithms are implemented in Rust._
- [rust-python-coverage](https://github.com/cjermain/rust-python-coverage) _Example PyO3 project with automated test coverage for Rust and Python._

View File

@ -39,13 +39,14 @@ fn configure_pyo3() -> Result<()> {
println!("{}", cfg)
}
// Emit cfgs like `thread_local_const_init`
// Emit cfgs like `invalid_from_utf8_lint`
print_feature_cfgs();
Ok(())
}
fn main() {
pyo3_build_config::print_expected_cfgs();
if let Err(e) = configure_pyo3() {
eprintln!("error: {}", e.report());
std::process::exit(1)

View File

@ -10,5 +10,5 @@ pyo3 = { path = "..", features = ["auto-initialize", "extension-module"] }
[[example]]
name = "decorator"
path = "decorator/src/lib.rs"
crate_type = ["cdylib"]
crate-type = ["cdylib"]
doc-scrape-examples = true

View File

@ -1,4 +1,4 @@
variable::set("PYO3_VERSION", "0.21.1");
variable::set("PYO3_VERSION", "0.23.0-dev");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/pyproject.toml", "pyproject.toml");
file::delete(".template");

View File

@ -2,7 +2,6 @@
use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use pyo3::types::PySlice;
use std::os::raw::c_long;
#[derive(FromPyObject)]
enum IntOrSlice<'py> {
@ -29,7 +28,7 @@ impl ExampleContainer {
} else if let Ok(slice) = key.downcast::<PySlice>() {
// METHOD 1 - the use PySliceIndices to help with bounds checking and for cases when only start or end are provided
// in this case the start/stop/step all filled in to give valid values based on the max_length given
let index = slice.indices(self.max_length as c_long).unwrap();
let index = slice.indices(self.max_length as isize).unwrap();
let _delta = index.stop - index.start;
// METHOD 2 - Do the getattr manually really only needed if you have some special cases for stop/_step not being present
@ -62,7 +61,7 @@ impl ExampleContainer {
fn __setitem__(&self, idx: IntOrSlice, value: u32) -> PyResult<()> {
match idx {
IntOrSlice::Slice(slice) => {
let index = slice.indices(self.max_length as c_long).unwrap();
let index = slice.indices(self.max_length as isize).unwrap();
println!(
"Got a slice! {}-{}, step: {}, value: {}",
index.start, index.stop, index.step, value
@ -76,8 +75,7 @@ impl ExampleContainer {
}
}
#[pymodule]
#[pyo3(name = "getitem")]
#[pymodule(name = "getitem")]
fn example(m: &Bound<'_, PyModule>) -> PyResult<()> {
// ? -https://github.com/PyO3/maturin/issues/475
m.add_class::<ExampleContainer>()?;

View File

@ -1,4 +1,4 @@
variable::set("PYO3_VERSION", "0.21.1");
variable::set("PYO3_VERSION", "0.23.0-dev");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/pyproject.toml", "pyproject.toml");
file::delete(".template");

View File

@ -1,4 +1,4 @@
variable::set("PYO3_VERSION", "0.21.1");
variable::set("PYO3_VERSION", "0.23.0-dev");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/plugin_api/Cargo.toml", "plugin_api/Cargo.toml");
file::delete(".template");

View File

@ -1,5 +1,6 @@
use core::sync::atomic::{AtomicU64, Ordering};
use core::{mem, ptr};
use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_uint, c_ulonglong, c_void};
use pyo3_ffi::*;
@ -27,10 +28,10 @@ unsafe extern "C" fn id_new(
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>(),
);
// We use pyo3-ffi's `c_str!` macro to create null-terminated literals because
// Rust's string literals are not null-terminated
// On Rust 1.77 or newer you can use `c"text"` instead.
PyErr_SetString(PyExc_TypeError, c_str!("Id() takes no arguments").as_ptr());
return ptr::null_mut();
}
@ -81,8 +82,12 @@ unsafe extern "C" fn id_richcompare(
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>());
let msg = CString::new(&*format!(
"unrecognized richcompare opcode {}",
unrecognized
))
.unwrap();
PyErr_SetString(PyExc_SystemError, msg.as_ptr());
return ptr::null_mut();
}
};
@ -101,7 +106,7 @@ static mut SLOTS: &[PyType_Slot] = &[
},
PyType_Slot {
slot: Py_tp_doc,
pfunc: "An id that is increased every time an instance is created\0".as_ptr()
pfunc: c_str!("An id that is increased every time an instance is created").as_ptr()
as *mut c_void,
},
PyType_Slot {
@ -123,7 +128,7 @@ static mut SLOTS: &[PyType_Slot] = &[
];
pub static mut ID_SPEC: PyType_Spec = PyType_Spec {
name: "sequential.Id\0".as_ptr().cast::<c_char>(),
name: c_str!("sequential.Id").as_ptr(),
basicsize: mem::size_of::<PyId>() as c_int,
itemsize: 0,
flags: (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE) as c_uint,

View File

@ -1,13 +1,11 @@
use core::{mem, ptr};
use pyo3_ffi::*;
use std::os::raw::{c_char, c_int, c_void};
use std::os::raw::{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_name: c_str!("sequential").as_ptr(),
m_doc: c_str!("A library for generating sequential ids, written in Rust.").as_ptr(),
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 },
@ -42,13 +40,13 @@ unsafe extern "C" fn sequential_exec(module: *mut PyObject) -> c_int {
if id_type.is_null() {
PyErr_SetString(
PyExc_SystemError,
"cannot locate type object\0".as_ptr().cast::<c_char>(),
c_str!("cannot locate type object").as_ptr(),
);
return -1;
}
(*state).id_type = id_type.cast::<PyTypeObject>();
PyModule_AddObjectRef(module, "Id\0".as_ptr().cast::<c_char>(), id_type)
PyModule_AddObjectRef(module, c_str!("Id").as_ptr(), id_type)
}
unsafe extern "C" fn sequential_traverse(

View File

@ -5,11 +5,13 @@ use std::thread;
use pyo3_ffi::*;
use sequential::PyInit_sequential;
static COMMAND: &'static str = "
static COMMAND: &'static str = c_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);
@ -19,10 +21,7 @@ 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),
);
let ret = PyImport_AppendInittab(c_str!("sequential").as_ptr(), Some(PyInit_sequential));
if ret == -1 {
return Err("could not add module to inittab".into());
}
@ -122,11 +121,8 @@ unsafe fn fetch() -> String {
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,
);
let code_obj =
Py_CompileString(COMMAND.as_ptr(), c_str!("program").as_ptr(), Py_file_input);
if code_obj.is_null() {
return Err(fetch());
}
@ -138,7 +134,7 @@ fn run_code() -> Result<u64, String> {
} else {
Py_DECREF(res_ptr);
}
let sum = PyDict_GetItemString(globals, "s\0".as_ptr().cast::<c_char>()); /* borrowed reference */
let sum = PyDict_GetItemString(globals, c_str!("s").as_ptr()); /* borrowed reference */
if sum.is_null() {
Py_DECREF(globals);
return Err("globals did not have `s`".into());

View File

@ -1,4 +1,4 @@
variable::set("PYO3_VERSION", "0.21.1");
variable::set("PYO3_VERSION", "0.23.0-dev");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/setup.cfg", "setup.cfg");
file::delete(".template");

View File

@ -5,10 +5,8 @@ use pyo3_ffi::*;
static mut MODULE_DEF: PyModuleDef = PyModuleDef {
m_base: PyModuleDef_HEAD_INIT,
m_name: "string_sum\0".as_ptr().cast::<c_char>(),
m_doc: "A Python module written in Rust.\0"
.as_ptr()
.cast::<c_char>(),
m_name: c_str!("string_sum").as_ptr(),
m_doc: c_str!("A Python module written in Rust.").as_ptr(),
m_size: 0,
m_methods: unsafe { METHODS as *const [PyMethodDef] as *mut PyMethodDef },
m_slots: std::ptr::null_mut(),
@ -19,14 +17,12 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef {
static mut METHODS: &[PyMethodDef] = &[
PyMethodDef {
ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(),
ml_name: c_str!("sum_as_string").as_ptr(),
ml_meth: PyMethodDefPointer {
_PyCFunctionFast: sum_as_string,
},
ml_flags: METH_FASTCALL,
ml_doc: "returns the sum of two integers as a string\0"
.as_ptr()
.cast::<c_char>(),
ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
},
// A zeroed PyMethodDef to mark the end of the array.
PyMethodDef::zeroed(),
@ -93,9 +89,7 @@ pub unsafe extern "C" fn sum_as_string(
if nargs != 2 {
PyErr_SetString(
PyExc_TypeError,
"sum_as_string expected 2 positional arguments\0"
.as_ptr()
.cast::<c_char>(),
c_str!("sum_as_string expected 2 positional arguments").as_ptr(),
);
return std::ptr::null_mut();
}
@ -119,7 +113,7 @@ pub unsafe extern "C" fn sum_as_string(
None => {
PyErr_SetString(
PyExc_OverflowError,
"arguments too large to add\0".as_ptr().cast::<c_char>(),
c_str!("arguments too large to add").as_ptr(),
);
std::ptr::null_mut()
}

View File

@ -1,4 +1,4 @@
variable::set("PYO3_VERSION", "0.21.1");
variable::set("PYO3_VERSION", "0.23.0-dev");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/pyproject.toml", "pyproject.toml");
file::delete(".template");

View File

@ -2,15 +2,20 @@
| Parameter | Description |
| :- | :- |
| `constructor` | This is currently only allowed on [variants of complex enums][params-constructor]. It allows customization of the generated class constructor for each variant. It uses the same syntax and supports the same options as the `signature` attribute of functions and methods. |
| <span style="white-space: pre">`crate = "some::path"`</span> | Path to import the `pyo3` crate, if it's not accessible at `::pyo3`. |
| `dict` | Gives instances of this class an empty `__dict__` to store custom attributes. |
| `eq` | Implements `__eq__` using the `PartialEq` implementation of the underlying Rust datatype. |
| `eq_int` | Implements `__eq__` using `__int__` for simple enums. |
| <span style="white-space: pre">`extends = BaseType`</span> | Use a custom baseclass. Defaults to [`PyAny`][params-1] |
| <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. |
| <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. |
| `get_all` | Generates getters for all fields of the pyclass. |
| `hash` | Implements `__hash__` using the `Hash` implementation of the underlying Rust datatype. |
| `mapping` | Inform PyO3 that this class is a [`Mapping`][params-mapping], and so leave its implementation of sequence C-API slots empty. |
| <span style="white-space: pre">`module = "module_name"`</span> | Python code will see the class as being defined in this module. Defaults to `builtins`. |
| <span style="white-space: pre">`name = "python_name"`</span> | Sets the name that Python sees this class as. Defaults to the name of the Rust struct. |
| `ord` | Implements `__lt__`, `__gt__`, `__le__`, & `__ge__` using the `PartialOrd` implementation of the underlying Rust datatype. *Requires `eq`* |
| `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". |
| `sequence` | Inform PyO3 that this class is a [`Sequence`][params-sequence], and so leave its C-API mapping length slot empty. |
| `set_all` | Generates setters for all fields of the pyclass. |
@ -39,5 +44,6 @@ struct MyClass {}
[params-4]: https://doc.rust-lang.org/std/rc/struct.Rc.html
[params-5]: https://doc.rust-lang.org/std/sync/struct.Arc.html
[params-6]: https://docs.python.org/3/library/weakref.html
[params-constructor]: https://pyo3.rs/latest/class.html#complex-enums
[params-mapping]: https://pyo3.rs/latest/class/protocols.html#mapping--sequence-types
[params-sequence]: https://pyo3.rs/latest/class/protocols.html#mapping--sequence-types

View File

@ -12,6 +12,7 @@ use futures::channel::oneshot;
use pyo3::prelude::*;
#[pyfunction]
#[pyo3(signature=(seconds, result=None))]
async fn sleep(seconds: f64, result: Option<PyObject>) -> Option<PyObject> {
let (tx, rx) = oneshot::channel();
thread::spawn(move || {

View File

@ -151,7 +151,7 @@ rustflags = [
]
```
Alternatively, on rust >= 1.56, one can include in `build.rs`:
Alternatively, one can include in `build.rs`:
```rust
fn main() {
@ -163,7 +163,7 @@ fn main() {
For more discussion on and workarounds for MacOS linking problems [see this issue](https://github.com/PyO3/pyo3/issues/1800#issuecomment-906786649).
Finally, don't forget that on MacOS the `extension-module` feature will cause `cargo test` to fail without the `--no-default-features` flag (see [the FAQ](https://pyo3.rs/main/faq.html#i-cant-run-cargo-test-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror)).
Finally, don't forget that on MacOS the `extension-module` feature will cause `cargo test` to fail without the `--no-default-features` flag (see [the FAQ](https://pyo3.rs/main/faq.html#i-cant-run-cargo-test-or-i-cant-build-in-a-cargo-workspace-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror)).
### The `extension-module` feature
@ -232,7 +232,7 @@ not work when compiling for `abi3`. These are:
If you want to embed the Python interpreter inside a Rust program, there are two modes in which this can be done: dynamically and statically. We'll cover each of these modes in the following sections. Each of them affect how you must distribute your program. Instead of learning how to do this yourself, you might want to consider using a project like [PyOxidizer] to ship your application and all of its dependencies in a single file.
PyO3 automatically switches between the two linking modes depending on whether the Python distribution you have configured PyO3 to use ([see above](#configuring-the-python-version)) contains a shared library or a static library. The static library is most often seen in Python distributions compiled from source without the `--enable-shared` configuration option. For example, this is the default for `pyenv` on macOS.
PyO3 automatically switches between the two linking modes depending on whether the Python distribution you have configured PyO3 to use ([see above](#configuring-the-python-version)) contains a shared library or a static library. The static library is most often seen in Python distributions compiled from source without the `--enable-shared` configuration option.
### Dynamically embedding the Python interpreter

View File

@ -37,14 +37,16 @@ struct Number(i32);
// PyO3 supports unit-only enums (which contain only unit variants)
// These simple enums behave similarly to Python's enumerations (enum.Enum)
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum {
Variant,
OtherVariant = 30, // PyO3 supports custom discriminants.
}
// PyO3 supports custom discriminants in unit-only enums
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum HttpResponse {
Ok = 200,
NotFound = 404,
@ -52,15 +54,18 @@ enum HttpResponse {
// ...
}
// PyO3 also supports enums with non-unit variants
// PyO3 also supports enums with Struct and Tuple variants
// These complex enums have sligtly different behavior from the simple enums above
// They are meant to work with instance checks and match statement patterns
// The variants can be mixed and matched
// Struct variants have named fields while tuple enums generate generic names for fields in order _0, _1, _2, ...
// Apart from this both types are functionally identical
#[pyclass]
enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
RegularPolygon { side_count: u32, radius: f64 },
Nothing {},
RegularPolygon(u32, f64),
Nothing(),
}
```
@ -249,7 +254,7 @@ fn return_myclass() -> Py<MyClass> {
let obj = return_myclass();
Python::with_gil(|py| {
Python::with_gil(move |py| {
let bound = obj.bind(py); // Py<MyClass>::bind returns &Bound<'py, MyClass>
let obj_ref = bound.borrow(); // Get PyRef<T>
assert_eq!(obj_ref.num, 1);
@ -280,6 +285,8 @@ let py_counter: Py<FrozenCounter> = Python::with_gil(|py| {
});
py_counter.get().value.fetch_add(1, Ordering::Relaxed);
Python::with_gil(move |_py| drop(py_counter));
```
Frozen classes are likely to become the default thereby guiding the PyO3 ecosystem towards a more deliberate application of interior mutability. Eventually, this should enable further optimizations of PyO3's internals and avoid downstream code paying the cost of interior mutability when it is not actually required.
@ -320,8 +327,12 @@ explicitly.
To get a parent class from a child, use [`PyRef`] instead of `&self` for methods,
or [`PyRefMut`] instead of `&mut self`.
Then you can access a parent class by `self_.as_ref()` as `&Self::BaseClass`,
or by `self_.into_super()` as `PyRef<Self::BaseClass>`.
Then you can access a parent class by `self_.as_super()` as `&PyRef<Self::BaseClass>`,
or by `self_.into_super()` as `PyRef<Self::BaseClass>` (and similar for the `PyRefMut`
case). For convenience, `self_.as_ref()` can also be used to get `&Self::BaseClass`
directly; however, this approach does not let you access base clases higher in the
inheritance hierarchy, for which you would need to chain multiple `as_super` or
`into_super` calls.
```rust
# use pyo3::prelude::*;
@ -338,7 +349,7 @@ impl BaseClass {
BaseClass { val1: 10 }
}
pub fn method(&self) -> PyResult<usize> {
pub fn method1(&self) -> PyResult<usize> {
Ok(self.val1)
}
}
@ -356,8 +367,8 @@ impl SubClass {
}
fn method2(self_: PyRef<'_, Self>) -> PyResult<usize> {
let super_ = self_.as_ref(); // Get &BaseClass
super_.method().map(|x| x * self_.val2)
let super_ = self_.as_super(); // Get &PyRef<BaseClass>
super_.method1().map(|x| x * self_.val2)
}
}
@ -374,11 +385,28 @@ impl SubSubClass {
}
fn method3(self_: PyRef<'_, Self>) -> PyResult<usize> {
let base = self_.as_super().as_super(); // Get &PyRef<'_, BaseClass>
base.method1().map(|x| x * self_.val3)
}
fn method4(self_: PyRef<'_, Self>) -> PyResult<usize> {
let v = self_.val3;
let super_ = self_.into_super(); // Get PyRef<'_, SubClass>
SubClass::method2(super_).map(|x| x * v)
}
fn get_values(self_: PyRef<'_, Self>) -> (usize, usize, usize) {
let val1 = self_.as_super().as_super().val1;
let val2 = self_.as_super().val2;
(val1, val2, self_.val3)
}
fn double_values(mut self_: PyRefMut<'_, Self>) {
self_.as_super().as_super().val1 *= 2;
self_.as_super().val2 *= 2;
self_.val3 *= 2;
}
#[staticmethod]
fn factory_method(py: Python<'_>, val: usize) -> PyResult<PyObject> {
let base = PyClassInitializer::from(BaseClass::new());
@ -393,7 +421,13 @@ impl SubSubClass {
}
# Python::with_gil(|py| {
# let subsub = pyo3::Py::new(py, SubSubClass::new()).unwrap();
# pyo3::py_run!(py, subsub, "assert subsub.method3() == 3000");
# pyo3::py_run!(py, subsub, "assert subsub.method1() == 10");
# pyo3::py_run!(py, subsub, "assert subsub.method2() == 150");
# pyo3::py_run!(py, subsub, "assert subsub.method3() == 200");
# pyo3::py_run!(py, subsub, "assert subsub.method4() == 3000");
# pyo3::py_run!(py, subsub, "assert subsub.get_values() == (10, 15, 20)");
# pyo3::py_run!(py, subsub, "assert subsub.double_values() == None");
# pyo3::py_run!(py, subsub, "assert subsub.get_values() == (20, 30, 40)");
# let subsub = SubSubClass::factory_method(py, 2).unwrap();
# let subsubsub = SubSubClass::factory_method(py, 3).unwrap();
# let cls = py.get_type_bound::<SubSubClass>();
@ -998,6 +1032,44 @@ impl MyClass {
Note that `text_signature` on `#[new]` is not compatible with compilation in
`abi3` mode until Python 3.10 or greater.
### Method receivers and lifetime elision
PyO3 supports writing instance methods using the normal method receivers for shared `&self` and unique `&mut self` references. This interacts with [lifetime elision][lifetime-elision] insofar as the lifetime of a such a receiver is assigned to all elided output lifetime parameters.
This is a good default for general Rust code where return values are more likely to borrow from the receiver than from the other arguments, if they contain any lifetimes at all. However, when returning bound references `Bound<'py, T>` in PyO3-based code, the GIL lifetime `'py` should usually be derived from a GIL token `py: Python<'py>` passed as an argument instead of the receiver.
Specifically, signatures like
```rust,ignore
fn frobnicate(&self, py: Python) -> Bound<Foo>;
```
will not work as they are inferred as
```rust,ignore
fn frobnicate<'a, 'py>(&'a self, py: Python<'py>) -> Bound<'a, Foo>;
```
instead of the intended
```rust,ignore
fn frobnicate<'a, 'py>(&'a self, py: Python<'py>) -> Bound<'py, Foo>;
```
and should usually be written as
```rust,ignore
fn frobnicate<'py>(&self, py: Python<'py>) -> Bound<'py, Foo>;
```
The same problem does not exist for `#[pyfunction]`s as the special case for receiver lifetimes does not apply and indeed a signature like
```rust,ignore
fn frobnicate(bar: &Bar, py: Python) -> Bound<Foo>;
```
will yield compiler error [E0106 "missing lifetime specifier"][compiler-error-e0106].
## `#[pyclass]` enums
Enum support in PyO3 comes in two flavors, depending on what kind of variants the enum has: simple and complex.
@ -1010,7 +1082,8 @@ PyO3 adds a class attribute for each variant, so you can access them in Python w
```rust
# use pyo3::prelude::*;
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum {
Variant,
OtherVariant,
@ -1032,7 +1105,8 @@ You can also convert your simple enums into `int`:
```rust
# use pyo3::prelude::*;
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum {
Variant,
OtherVariant = 10,
@ -1044,8 +1118,6 @@ Python::with_gil(|py| {
pyo3::py_run!(py, cls x, r#"
assert int(cls.Variant) == x
assert int(cls.OtherVariant) == 10
assert cls.OtherVariant == 10 # You can also compare against int.
assert 10 == cls.OtherVariant
"#)
})
```
@ -1054,7 +1126,8 @@ PyO3 also provides `__repr__` for enums:
```rust
# use pyo3::prelude::*;
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum{
Variant,
OtherVariant,
@ -1074,7 +1147,8 @@ All methods defined by PyO3 can be overridden. For example here's how you overri
```rust
# use pyo3::prelude::*;
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum {
Answer = 42,
}
@ -1096,7 +1170,8 @@ Enums and their variants can also be renamed using `#[pyo3(name)]`.
```rust
# use pyo3::prelude::*;
#[pyclass(name = "RenamedEnum")]
#[pyclass(eq, eq_int, name = "RenamedEnum")]
#[derive(PartialEq)]
enum MyEnum {
#[pyo3(name = "UPPERCASE")]
Variant,
@ -1112,6 +1187,32 @@ Python::with_gil(|py| {
})
```
Ordering of enum variants is optionally added using `#[pyo3(ord)]`.
*Note: Implementation of the `PartialOrd` trait is required when passing the `ord` argument. If not implemented, a compile time error is raised.*
```rust
# use pyo3::prelude::*;
#[pyclass(eq, ord)]
#[derive(PartialEq, PartialOrd)]
enum MyEnum{
A,
B,
C,
}
Python::with_gil(|py| {
let cls = py.get_type_bound::<MyEnum>();
let a = Py::new(py, MyEnum::A).unwrap();
let b = Py::new(py, MyEnum::B).unwrap();
let c = Py::new(py, MyEnum::C).unwrap();
pyo3::py_run!(py, cls a b c, r#"
assert (a < b) == True
assert (c <= b) == False
assert (c > a) == True
"#)
})
```
You may not use enums as a base class or let enums inherit from other classes.
```rust,compile_fail
@ -1140,7 +1241,7 @@ enum BadSubclass {
An enum is complex if it has any non-unit (struct or tuple) variants.
Currently PyO3 supports only struct variants in a complex enum. Support for unit and tuple variants is planned.
PyO3 supports only struct and tuple variants in a complex enum. Unit variants aren't supported at present (the recommendation is to use an empty tuple enum instead).
PyO3 adds a class attribute for each variant, which may be used to construct values and in match patterns. PyO3 also provides getter methods for all fields of each variant.
@ -1150,14 +1251,14 @@ PyO3 adds a class attribute for each variant, which may be used to construct val
enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
RegularPolygon { side_count: u32, radius: f64 },
RegularPolygon(u32, f64),
Nothing { },
}
# #[cfg(Py_3_10)]
Python::with_gil(|py| {
let circle = Shape::Circle { radius: 10.0 }.into_py(py);
let square = Shape::RegularPolygon { side_count: 4, radius: 10.0 }.into_py(py);
let square = Shape::RegularPolygon(4, 10.0).into_py(py);
let cls = py.get_type_bound::<Shape>();
pyo3::py_run!(py, circle square cls, r#"
assert isinstance(circle, cls)
@ -1166,8 +1267,8 @@ Python::with_gil(|py| {
assert isinstance(square, cls)
assert isinstance(square, cls.RegularPolygon)
assert square.side_count == 4
assert square.radius == 10.0
assert square[0] == 4 # Gets _0 field
assert square[1] == 10.0 # Gets _1 field
def count_vertices(cls, shape):
match shape:
@ -1175,7 +1276,7 @@ Python::with_gil(|py| {
return 0
case cls.Rectangle():
return 4
case cls.RegularPolygon(side_count=n):
case cls.RegularPolygon(n):
return n
case cls.Nothing():
return 0
@ -1205,6 +1306,46 @@ Python::with_gil(|py| {
})
```
The constructor of each generated class can be customized using the `#[pyo3(constructor = (...))]` attribute. This uses the same syntax as the [`#[pyo3(signature = (...))]`](function/signature.md)
attribute on function and methods and supports the same options. To apply this attribute simply place it on top of a variant in a `#[pyclass]` complex enum as shown below:
```rust
# use pyo3::prelude::*;
#[pyclass]
enum Shape {
#[pyo3(constructor = (radius=1.0))]
Circle { radius: f64 },
#[pyo3(constructor = (*, width, height))]
Rectangle { width: f64, height: f64 },
#[pyo3(constructor = (side_count, radius=1.0))]
RegularPolygon { side_count: u32, radius: f64 },
Nothing { },
}
# #[cfg(Py_3_10)]
Python::with_gil(|py| {
let cls = py.get_type_bound::<Shape>();
pyo3::py_run!(py, cls, r#"
circle = cls.Circle()
assert isinstance(circle, cls)
assert isinstance(circle, cls.Circle)
assert circle.radius == 1.0
square = cls.Rectangle(width = 1, height = 1)
assert isinstance(square, cls)
assert isinstance(square, cls.Rectangle)
assert square.width == 1
assert square.height == 1
hexagon = cls.RegularPolygon(6)
assert isinstance(hexagon, cls)
assert isinstance(hexagon, cls.RegularPolygon)
assert hexagon.side_count == 6
assert hexagon.radius == 1
"#)
})
```
## Implementation details
The `#[pyclass]` macros rely on a lot of conditional code generation: each `#[pyclass]` can optionally have a `#[pymethods]` block.
@ -1227,6 +1368,7 @@ struct MyClass {
impl pyo3::types::DerefToPyAny for MyClass {}
# #[allow(deprecated)]
# #[cfg(feature = "gil-refs")]
unsafe impl pyo3::type_object::HasPyGilRef for MyClass {
type AsRefTarget = pyo3::PyCell<Self>;
}
@ -1301,7 +1443,7 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
static DOC: pyo3::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = pyo3::sync::GILOnceCell::new();
DOC.get_or_try_init(py, || {
let collector = PyClassImplCollector::<Self>::new();
build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, "\0", collector.new_text_signature())
build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, pyo3::ffi::c_str!(""), collector.new_text_signature())
}).map(::std::ops::Deref::deref)
}
}
@ -1329,3 +1471,6 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
[classattr]: https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables
[`multiple-pymethods`]: features.md#multiple-pymethods
[lifetime-elision]: https://doc.rust-lang.org/reference/lifetime-elision.html
[compiler-error-e0106]: https://doc.rust-lang.org/error_codes/E0106.html

View File

@ -210,7 +210,7 @@ use std::hash::{Hash, Hasher};
use pyo3::exceptions::{PyValueError, PyZeroDivisionError};
use pyo3::prelude::*;
use pyo3::class::basic::CompareOp;
use pyo3::types::PyComplex;
use pyo3::types::{PyComplex, PyString};
fn wrap(obj: &Bound<'_, PyAny>) -> PyResult<i32> {
let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
@ -231,7 +231,7 @@ impl Number {
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
// Get the class name dynamically in case `Number` is subclassed
let class_name: String = slf.get_type().qualname()?;
let class_name: Bound<'_, PyString> = slf.get_type().qualname()?;
Ok(format!("{}({})", class_name, slf.borrow().0))
}

View File

@ -80,6 +80,7 @@ the subclass name. This is typically done in Python code by accessing
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyString;
#
# #[pyclass]
# struct Number(i32);
@ -88,7 +89,7 @@ the subclass name. This is typically done in Python code by accessing
impl Number {
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
// This is the equivalent of `self.__class__.__name__` in Python.
let class_name: String = slf.get_type().qualname()?;
let class_name: Bound<'_, PyString> = slf.get_type().qualname()?;
// To access fields of the Rust struct, we need to borrow the `PyCell`.
Ok(format!("{}({})", class_name, slf.borrow().0))
}
@ -121,6 +122,19 @@ impl Number {
}
}
```
To implement `__hash__` using the Rust [`Hash`] trait implementation, the `hash` option can be used.
This option is only available for `frozen` classes to prevent accidental hash changes from mutating the object. If you need
an `__hash__` implementation for a mutable class, use the manual method from above. This option also requires `eq`: According to the
[Python docs](https://docs.python.org/3/reference/datamodel.html#object.__hash__) "If a class does not define an `__eq__()`
method it should not define a `__hash__()` operation either"
```rust
# use pyo3::prelude::*;
#
#[pyclass(frozen, eq, hash)]
#[derive(PartialEq, Hash)]
struct Number(i32);
```
> **Note**: When implementing `__hash__` and comparisons, it is important that the following property holds:
>
@ -226,6 +240,26 @@ impl Number {
# }
```
To implement `__eq__` using the Rust [`PartialEq`] trait implementation, the `eq` option can be used.
```rust
# use pyo3::prelude::*;
#
#[pyclass(eq)]
#[derive(PartialEq)]
struct Number(i32);
```
To implement `__lt__`, `__le__`, `__gt__`, & `__ge__` using the Rust `PartialOrd` trait implementation, the `ord` option can be used. *Note: Requires `eq`.*
```rust
# use pyo3::prelude::*;
#
#[pyclass(eq, ord)]
#[derive(PartialEq, PartialOrd)]
struct Number(i32);
```
### Truthyness
We'll consider `Number` to be `True` if it is nonzero:
@ -252,6 +286,7 @@ use std::hash::{Hash, Hasher};
use pyo3::prelude::*;
use pyo3::class::basic::CompareOp;
use pyo3::types::PyString;
#[pyclass]
struct Number(i32);
@ -264,7 +299,7 @@ impl Number {
}
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
let class_name: String = slf.get_type().qualname()?;
let class_name: Bound<'_, PyString> = slf.get_type().qualname()?;
Ok(format!("{}({})", class_name, slf.borrow().0))
}
@ -305,3 +340,4 @@ fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
[`Hasher`]: https://doc.rust-lang.org/std/hash/trait.Hasher.html
[`DefaultHasher`]: https://doc.rust-lang.org/std/collections/hash_map/struct.DefaultHasher.html
[SipHash]: https://en.wikipedia.org/wiki/SipHash
[`PartialEq`]: https://doc.rust-lang.org/stable/std/cmp/trait.PartialEq.html

View File

@ -1,20 +1,27 @@
# Magic methods and slots
# Class customizations
Python's object model defines several protocols for different object behavior, such as the sequence, mapping, and number protocols. You may be familiar with implementing these protocols in Python classes by "magic" methods, such as `__str__` or `__repr__`. Because of the double-underscores surrounding their name, these are also known as "dunder" methods.
Python's object model defines several protocols for different object behavior, such as the sequence, mapping, and number protocols. Python classes support these protocols by implementing "magic" methods, such as `__str__` or `__repr__`. Because of the double-underscores surrounding their name, these are also known as "dunder" methods.
In the Python C-API which PyO3 is implemented upon, many of these magic methods have to be placed into special "slots" on the class type object, as covered in the previous section.
PyO3 makes it possible for every magic method to be implemented in `#[pymethods]` just as they would be done in a regular Python class, with a few notable differences:
- `__new__` and `__init__` are replaced by the [`#[new]` attribute](../class.md#constructor).
- `__del__` is not yet supported, but may be in the future.
- `__buffer__` and `__release_buffer__` are currently not supported and instead PyO3 supports [`__getbuffer__` and `__releasebuffer__`](#buffer-objects) methods (these predate [PEP 688](https://peps.python.org/pep-0688/#python-level-buffer-protocol)), again this may change in the future.
- PyO3 adds [`__traverse__` and `__clear__`](#garbage-collector-integration) methods for controlling garbage collection.
- The Python C-API which PyO3 is implemented upon requires many magic methods to have a specific function signature in C and be placed into special "slots" on the class type object. This limits the allowed argument and return types for these methods. They are listed in detail in the section below.
If a function name in `#[pymethods]` is a recognised magic method, it will be automatically placed into the correct slot in the Python type object. The function name is taken from the usual rules for naming `#[pymethods]`: the `#[pyo3(name = "...")]` attribute is used if present, otherwise the Rust function name is used.
If a magic method is not on the list above (for example `__init_subclass__`), then it should just work in PyO3. If this is not the case, please file a bug report.
The magic methods handled by PyO3 are very similar to the standard Python ones on [this page](https://docs.python.org/3/reference/datamodel.html#special-method-names) - in particular they are the subset which have slots as [defined here](https://docs.python.org/3/c-api/typeobj.html). Some of the slots do not have a magic method in Python, which leads to a few additional magic methods defined only in PyO3:
- Magic methods for garbage collection
- Magic methods for the buffer protocol
## Magic Methods handled by PyO3
If a function name in `#[pymethods]` is a magic method which is known to need special handling, it will be automatically placed into the correct slot in the Python type object. The function name is taken from the usual rules for naming `#[pymethods]`: the `#[pyo3(name = "...")]` attribute is used if present, otherwise the Rust function name is used.
The magic methods handled by PyO3 are very similar to the standard Python ones on [this page](https://docs.python.org/3/reference/datamodel.html#special-method-names) - in particular they are the subset which have slots as [defined here](https://docs.python.org/3/c-api/typeobj.html).
When PyO3 handles a magic method, a couple of changes apply compared to other `#[pymethods]`:
- The Rust function signature is restricted to match the magic method.
- The `#[pyo3(signature = (...)]` and `#[pyo3(text_signature = "...")]` attributes are not allowed.
The following sections list of all magic methods PyO3 currently handles. The
The following sections list all magic methods for which PyO3 implements the necessary special handling. The
given signatures should be interpreted as follows:
- All methods take a receiver as first argument, shown as `<self>`. It can be
`&self`, `&mut self` or a `Bound` reference like `self_: PyRef<'_, Self>` and
@ -31,7 +38,6 @@ given signatures should be interpreted as follows:
checked by the Python interpreter. For example, `__str__` needs to return a
string object. This is indicated by `object (Python type)`.
### Basic object customization
- `__str__(<self>) -> object (str)`

View File

@ -19,6 +19,7 @@ The table below contains the Python type and the corresponding function argument
| `int` | `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize`, `num_bigint::BigInt`[^1], `num_bigint::BigUint`[^1] | `PyLong` |
| `float` | `f32`, `f64` | `PyFloat` |
| `complex` | `num_complex::Complex`[^2] | `PyComplex` |
| `fractions.Fraction`| `num_rational::Ratio`[^8] | - |
| `list[T]` | `Vec<T>` | `PyList` |
| `dict[K, V]` | `HashMap<K, V>`, `BTreeMap<K, V>`, `hashbrown::HashMap<K, V>`[^3], `indexmap::IndexMap<K, V>`[^4] | `PyDict` |
| `tuple[T, U]` | `(T, U)`, `Vec<T>` | `PyTuple` |
@ -113,3 +114,5 @@ Finally, the following Rust types are also able to convert to Python as return v
[^6]: Requires the `chrono-tz` optional feature.
[^7]: Requires the `rust_decimal` optional feature.
[^8]: Requires the `num-rational` optional feature.

View File

@ -265,7 +265,7 @@ use pyo3::prelude::*;
#[derive(FromPyObject)]
# #[derive(Debug)]
enum RustyEnum<'a> {
enum RustyEnum<'py> {
Int(usize), // input is a positive int
String(String), // input is a string
IntTuple(usize, usize), // input is a 2-tuple with positive ints
@ -284,7 +284,7 @@ enum RustyEnum<'a> {
b: usize,
},
#[pyo3(transparent)]
CatchAll(&'a PyAny), // This extraction never fails
CatchAll(Bound<'py, PyAny>), // This extraction never fails
}
#
# use pyo3::types::{PyBytes, PyString};
@ -394,7 +394,7 @@ enum RustyEnum<'a> {
# assert_eq!(
# b"text",
# match rust_thing {
# RustyEnum::CatchAll(i) => i.downcast::<PyBytes>()?.as_bytes(),
# RustyEnum::CatchAll(ref i) => i.downcast::<PyBytes>()?.as_bytes(),
# other => unreachable!("Error extracting: {:?}", other),
# }
# );

View File

@ -120,7 +120,7 @@ Export an async function that makes use of `async-std`:
use pyo3::{prelude::*, wrap_pyfunction};
#[pyfunction]
fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>>> {
fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>> {
pyo3_asyncio::async_std::future_into_py(py, async {
async_std::task::sleep(std::time::Duration::from_secs(1)).await;
Ok(Python::with_gil(|py| py.None()))
@ -129,9 +129,7 @@ fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>>> {
#[pymodule]
fn my_async_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;
Ok(())
m.add_function(wrap_pyfunction!(rust_sleep, m)?)
}
```
@ -152,8 +150,7 @@ fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>>> {
#[pymodule]
fn my_async_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;
Ok(())
m.add_function(wrap_pyfunction!(rust_sleep, m)?)
}
```
@ -257,7 +254,7 @@ async def py_sleep():
await_coro(py_sleep())
```
If for you wanted to pass a callable function to the `#[pyfunction]` instead, (i.e. the last line becomes `await_coro(py_sleep))`, then the above example needs to be tweaked to first call the callable to get the coroutine:
If you wanted to pass a callable function to the `#[pyfunction]` instead, (i.e. the last line becomes `await_coro(py_sleep))`, then the above example needs to be tweaked to first call the callable to get the coroutine:
```rust
#[pyfunction]

View File

@ -128,5 +128,5 @@ defines exceptions for several standard library modules.
[`PyErr`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html
[`PyResult`]: {{#PYO3_DOCS_URL}}/pyo3/type.PyResult.html
[`PyErr::from_value`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html#method.from_value
[`PyAny::is_instance`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html#method.is_instance
[`PyAny::is_instance_of`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html#method.is_instance_of
[`PyAny::is_instance`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.is_instance
[`PyAny::is_instance_of`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.is_instance_of

View File

@ -127,12 +127,10 @@ If you don't want that cloning to happen, a workaround is to allocate the field
```rust
# use pyo3::prelude::*;
#[pyclass]
#[derive(Clone)]
struct Inner {/* fields omitted */}
#[pyclass]
struct Outer {
#[pyo3(get)]
inner: Py<Inner>,
}
@ -144,6 +142,11 @@ impl Outer {
inner: Py::new(py, Inner {})?,
})
}
#[getter]
fn inner(&self, py: Python<'_>) -> Py<Inner> {
self.inner.clone_ref(py)
}
}
```
This time `a` and `b` *are* the same object:

View File

@ -57,12 +57,6 @@ This feature adds support for `async fn` in `#[pyfunction]` and `#[pymethods]`.
The feature has some unfinished refinements and performance improvements. To help finish this off, see [issue #1632](https://github.com/PyO3/pyo3/issues/1632) and its associated draft PRs.
### `experimental-declarative-modules`
This feature allows to declare Python modules using `#[pymodule] mod my_module { ... }` syntax.
The feature has some unfinished refinements and edge cases. To help finish this off, see [issue #3900](https://github.com/PyO3/pyo3/issues/3900).
### `experimental-inspect`
This feature adds the `pyo3::inspect` module, as well as `IntoPy::type_output` and `FromPyObject::type_input` APIs to produce Python type "annotations" for Rust types.
@ -75,6 +69,14 @@ This feature is a backwards-compatibility feature to allow continued use of the
This feature and the APIs it enables is expected to be removed in a future PyO3 version.
### `py-clone`
This feature was introduced to ease migration. It was found that delayed reference counts cannot be made sound and hence `Clon`ing an instance of `Py<T>` must panic without the GIL being held. To avoid migrations introducing new panics without warning, the `Clone` implementation itself is now gated behind this feature.
### `pyo3_disable_reference_pool`
This is a performance-oriented conditional compilation flag, e.g. [set via `$RUSTFLAGS`][set-configuration-options], which disabled the global reference pool and the assocaited overhead for the crossing the Python-Rust boundary. However, if enabled, `Drop`ping an instance of `Py<T>` without the GIL being held will abort the process.
### `macros`
This feature enables a dependency on the `pyo3-macros` crate, which provides the procedural macros portion of PyO3's API:
@ -93,9 +95,9 @@ These macros require a number of dependencies which may not be needed by users w
### `multiple-pymethods`
This feature enables a dependency on `inventory`, which enables each `#[pyclass]` to have more than one `#[pymethods]` block. This feature also requires a minimum Rust version of 1.62 due to limitations in the `inventory` crate.
This feature enables each `#[pyclass]` to have more than one `#[pymethods]` block.
Most users should only need a single `#[pymethods]` per `#[pyclass]`. In addition, not all platforms (e.g. Wasm) are supported by `inventory`. For this reason this feature is not enabled by default, meaning fewer dependencies and faster compilation for the majority of users.
Most users should only need a single `#[pymethods]` per `#[pyclass]`. In addition, not all platforms (e.g. Wasm) are supported by `inventory`, which is used in the implementation of the feature. For this reason this feature is not enabled by default, meaning fewer dependencies and faster compilation for the majority of users.
See [the `#[pyclass]` implementation details](class.md#implementation-details) for more information.
@ -157,6 +159,10 @@ Adds a dependency on [num-bigint](https://docs.rs/num-bigint) and enables conver
Adds a dependency on [num-complex](https://docs.rs/num-complex) and enables conversions into its [`Complex`](https://docs.rs/num-complex/latest/num_complex/struct.Complex.html) type.
### `num-rational`
Adds a dependency on [num-rational](https://docs.rs/num-rational) and enables conversions into its [`Ratio`](https://docs.rs/num-rational/latest/num_rational/struct.Ratio.html) type.
### `rust_decimal`
Adds a dependency on [rust_decimal](https://docs.rs/rust_decimal) and enables conversions into its [`Decimal`](https://docs.rs/rust_decimal/latest/rust_decimal/struct.Decimal.html) type.
@ -191,3 +197,5 @@ 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.
[set-configuration-options]: https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options

View File

@ -14,8 +14,7 @@ fn double(x: usize) -> usize {
#[pymodule]
fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(double, m)?)?;
Ok(())
m.add_function(wrap_pyfunction!(double, m)?)
}
```
@ -56,8 +55,7 @@ The `#[pyo3]` attribute can be used to modify properties of the generated Python
#[pymodule]
fn module_with_functions(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(no_args_py, m)?)?;
Ok(())
m.add_function(wrap_pyfunction!(no_args_py, m)?)
}
# Python::with_gil(|py| {
@ -113,8 +111,7 @@ The `#[pyo3]` attribute can be used on individual arguments to modify properties
use pyo3::prelude::*;
fn get_length(obj: &Bound<'_, PyAny>) -> PyResult<usize> {
let length = obj.len()?;
Ok(length)
obj.len()
}
#[pyfunction]
@ -204,8 +201,7 @@ fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> {
x * 2
}
m.add_function(wrap_pyfunction!(double, m)?)?;
Ok(())
m.add_function(wrap_pyfunction!(double, m)?)
}
```

View File

@ -22,8 +22,7 @@ fn num_kwds(kwds: Option<&Bound<'_, PyDict>>) -> usize {
#[pymodule]
fn module_with_functions(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(num_kwds, m)?).unwrap();
Ok(())
m.add_function(wrap_pyfunction!(num_kwds, m)?)
}
```
@ -121,9 +120,22 @@ num=-1
## Trailing optional arguments
<div class="warning">
⚠️ Warning: This behaviour is being phased out 🛠️
The special casing of trailing optional arguments is deprecated. In a future `pyo3` version, arguments of type `Option<..>` will share the same behaviour as other arguments, they are required unless a default is set using `#[pyo3(signature = (...))]`.
This is done to better align the Python and Rust definition of such functions and make it more intuitive to rewrite them from Python in Rust. Specifically `def some_fn(a: int, b: Optional[int]): ...` will not automatically default `b` to `none`, but requires an explicit default if desired, where as in current `pyo3` it is handled the other way around.
During the migration window a `#[pyo3(signature = (...))]` will be required to silence the deprecation warning. After support for trailing optional arguments is fully removed, the signature attribute can be removed if all arguments should be required.
</div>
As a convenience, functions without a `#[pyo3(signature = (...))]` option will treat trailing `Option<T>` arguments as having a default of `None`. In the example below, PyO3 will create `increment` with a signature of `increment(x, amount=None)`.
```rust
#![allow(deprecated)]
use pyo3::prelude::*;
/// Returns a copy of `x` increased by `amount`.

View File

@ -6,7 +6,7 @@ To get started using PyO3 you will need three things: a Rust toolchain, a Python
## Rust
First, make sure you have Rust installed on your system. If you haven't already done so, try following the instructions [here](https://www.rust-lang.org/tools/install). PyO3 runs on both the `stable` and `nightly` versions so you can choose whichever one fits you best. The minimum required Rust version is 1.56.
First, make sure you have Rust installed on your system. If you haven't already done so, try following the instructions [here](https://www.rust-lang.org/tools/install). PyO3 runs on both the `stable` and `nightly` versions so you can choose whichever one fits you best. The minimum required Rust version is 1.63.
If you can run `rustc --version` and the version is new enough you're good to go!
@ -18,19 +18,14 @@ To use PyO3, you need at least Python 3.7. While you can simply use the default
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.)
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"
```
It can be useful to keep the sources used when installing using `pyenv` so that future debugging can see the original source files. This can be done by passing the `--keep` flag as part of the `pyenv install` command.
For example:
```bash
env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.12
pyenv install 3.12 --keep
```
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](./building-and-distribution.md#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 way you already install Python packages.
@ -164,8 +159,7 @@ fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
/// import the module.
#[pymodule]
fn pyo3_example(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
m.add_function(wrap_pyfunction!(sum_as_string, m)?)
}
```
@ -181,3 +175,7 @@ $ python
```
For more instructions on how to use Python code from Rust, see the [Python from Rust](python-from-rust.md) page.
## Maturin Import Hook
In development, any changes in the code would require running `maturin develop` before testing. To streamline the development process, you may want to install [Maturin Import Hook](https://github.com/PyO3/maturin-import-hook) which will run `maturin develop` automatically when the library with code changes is being imported.

View File

@ -34,9 +34,11 @@ held. (If PyO3 could not assume this, every PyO3 API would need to take a
very simple and easy-to-understand programs like this:
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
# #[cfg(feature = "gil-refs")]
Python::with_gil(|py| -> PyResult<()> {
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
let hello = py
@ -57,9 +59,11 @@ it owns are decreased, releasing them to the Python garbage collector. Most
of the time we don't have to think about this, but consider the following:
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
# #[cfg(feature = "gil-refs")]
Python::with_gil(|py| -> PyResult<()> {
for _ in 0..10 {
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
@ -96,9 +100,11 @@ In general we don't want unbounded memory growth during loops! One workaround
is to acquire and release the GIL with each iteration of the loop.
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
# #[cfg(feature = "gil-refs")]
for _ in 0..10 {
Python::with_gil(|py| -> PyResult<()> {
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
@ -118,9 +124,11 @@ times. Another workaround is to work with the `GILPool` object directly, but
this is unsafe.
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
# #[cfg(feature = "gil-refs")]
Python::with_gil(|py| -> PyResult<()> {
for _ in 0..10 {
#[allow(deprecated)] // `new_pool` is not needed in code not using the GIL Refs API
@ -146,8 +154,7 @@ at the end of each loop iteration, before the `with_gil()` closure ends.
When doing this, you must be very careful to ensure that once the `GILPool` is
dropped you do not retain access to any owned references created after the
`GILPool` was created. Read the
[documentation for `Python::new_pool()`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.new_pool)
`GILPool` was created. Read the documentation for `Python::new_pool()`
for more information on safety.
This memory management can also be applicable when writing extension modules.
@ -177,9 +184,11 @@ What happens to the memory when the last `Py<PyAny>` is dropped and its
reference count reaches zero? It depends whether or not we are holding the GIL.
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
# #[cfg(feature = "gil-refs")]
Python::with_gil(|py| -> PyResult<()> {
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
let hello: Py<PyString> = py.eval("\"Hello World!\"", None, None)?.extract()?;
@ -203,9 +212,13 @@ This example wasn't very interesting. We could have just used a GIL-bound
we are *not* holding the GIL?
```rust
# #![allow(unused_imports, dead_code)]
# #[cfg(not(pyo3_disable_reference_pool))] {
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
# #[cfg(feature = "gil-refs")]
# {
let hello: Py<PyString> = Python::with_gil(|py| {
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
py.eval("\"Hello World!\"", None, None)?.extract()
@ -224,22 +237,28 @@ Python::with_gil(|py|
// Memory for `hello` is released here.
# ()
);
# }
# Ok(())
# }
# }
```
When `hello` is dropped *nothing* happens to the pointed-to memory on Python's
heap because nothing _can_ happen if we're not holding the GIL. Fortunately,
the memory isn't leaked. PyO3 keeps track of the memory internally and will
release it the next time we acquire the GIL.
the memory isn't leaked. If the `pyo3_disable_reference_pool` conditional compilation flag
is not enabled, PyO3 keeps track of the memory internally and will release it
the next time we acquire the GIL.
We can avoid the delay in releasing memory if we are careful to drop the
`Py<Any>` while the GIL is held.
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
# #[cfg(feature = "gil-refs")]
# {
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
let hello: Py<PyString> =
Python::with_gil(|py| py.eval("\"Hello World!\"", None, None)?.extract())?;
@ -252,6 +271,7 @@ Python::with_gil(|py| {
}
drop(hello); // Memory released here.
});
# }
# Ok(())
# }
```
@ -263,9 +283,12 @@ that rather than being released immediately, the memory will not be released
until the GIL is dropped.
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
# #[cfg(feature = "gil-refs")]
# {
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
let hello: Py<PyString> =
Python::with_gil(|py| py.eval("\"Hello World!\"", None, None)?.extract())?;
@ -280,6 +303,7 @@ Python::with_gil(|py| {
// Do more stuff...
// Memory released here at end of `with_gil()` closure.
});
# }
# Ok(())
# }
```

View File

@ -3,10 +3,155 @@
This guide can help you upgrade code through breaking changes from one PyO3 version to the next.
For a detailed list of all changes, see the [CHANGELOG](changelog.md).
## from 0.20.* to 0.21
## from 0.21.* to 0.22
### Deprecation of `gil-refs` feature continues
<details open>
<summary><small>Click to expand</small></summary>
Following the introduction of the "Bound" API in PyO3 0.21 and the planned removal of the "GIL Refs" API, all functionality related to GIL Refs is now gated behind the `gil-refs` feature and emits a deprecation warning on use.
See <a href="#from-021-to-022">the 0.21 migration entry</a> for help upgrading.
</details>
### Deprecation of implicit default for trailing optional arguments
<details open>
<summary><small>Click to expand</small></summary>
With `pyo3` 0.22 the implicit `None` default for trailing `Option<T>` type argument is deprecated. To migrate, place a `#[pyo3(signature = (...))]` attribute on affected functions or methods and specify the desired behavior.
The migration warning specifies the corresponding signature to keep the current behavior. With 0.23 the signature will be required for any function containing `Option<T>` type parameters to prevent accidental
and unnoticed changes in behavior. With 0.24 this restriction will be lifted again and `Option<T>` type arguments will be treated as any other argument _without_ special handling.
Before:
```rust
# #![allow(deprecated, dead_code)]
# use pyo3::prelude::*;
#[pyfunction]
fn increment(x: u64, amount: Option<u64>) -> u64 {
x + amount.unwrap_or(1)
}
```
After:
```rust
# #![allow(dead_code)]
# use pyo3::prelude::*;
#[pyfunction]
#[pyo3(signature = (x, amount=None))]
fn increment(x: u64, amount: Option<u64>) -> u64 {
x + amount.unwrap_or(1)
}
```
</details>
### `Py::clone` is now gated behind the `py-clone` feature
<details open>
<summary><small>Click to expand</small></summary>
If you rely on `impl<T> Clone for Py<T>` to fulfil trait requirements imposed by existing Rust code written without PyO3-based code in mind, the newly introduced feature `py-clone` must be enabled.
However, take care to note that the behaviour is different from previous versions. If `Clone` was called without the GIL being held, we tried to delay the application of these reference count increments until PyO3-based code would re-acquire it. This turned out to be impossible to implement in a sound manner and hence was removed. Now, if `Clone` is called without the GIL being held, we panic instead for which calling code might not be prepared.
Related to this, we also added a `pyo3_disable_reference_pool` conditional compilation flag which removes the infrastructure necessary to apply delayed reference count decrements implied by `impl<T> Drop for Py<T>`. They do not appear to be a soundness hazard as they should lead to memory leaks in the worst case. However, the global synchronization adds significant overhead to cross the Python-Rust boundary. Enabling this feature will remove these costs and make the `Drop` implementation abort the process if called without the GIL being held instead.
</details>
### Require explicit opt-in for comparison for simple enums
<details open>
<summary><small>Click to expand</small></summary>
With `pyo3` 0.22 the new `#[pyo3(eq)]` options allows automatic implementation of Python equality using Rust's `PartialEq`. Previously simple enums automatically implemented equality in terms of their discriminants. To make PyO3 more consistent, this automatic equality implementation is deprecated in favour of having opt-ins for all `#[pyclass]` types. Similarly, simple enums supported comparison with integers, which is not covered by Rust's `PartialEq` derive, so has been split out into the `#[pyo3(eq_int)]` attribute.
To migrate, place a `#[pyo3(eq, eq_int)]` attribute on simple enum classes.
Before:
```rust
# #![allow(deprecated, dead_code)]
# use pyo3::prelude::*;
#[pyclass]
enum SimpleEnum {
VariantA,
VariantB = 42,
}
```
After:
```rust
# #![allow(dead_code)]
# use pyo3::prelude::*;
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum SimpleEnum {
VariantA,
VariantB = 42,
}
```
</details>
### `PyType::name` reworked to better match Python `__name__`
<details open>
<summary><small>Click to expand</small></summary>
This function previously would try to read directly from Python type objects' C API field (`tp_name`), in which case it
would return a `Cow::Borrowed`. However the contents of `tp_name` don't have well-defined semantics.
Instead `PyType::name()` now returns the equivalent of Python `__name__` and returns `PyResult<Bound<'py, PyString>>`.
The closest equivalent to PyO3 0.21's version of `PyType::name()` has been introduced as a new function `PyType::fully_qualified_name()`,
which is equivalent to `__module__` and `__qualname__` joined as `module.qualname`.
Before:
```rust,ignore
# #![allow(deprecated, dead_code)]
# use pyo3::prelude::*;
# use pyo3::types::{PyBool};
# fn main() -> PyResult<()> {
Python::with_gil(|py| {
let bool_type = py.get_type_bound::<PyBool>();
let name = bool_type.name()?.into_owned();
println!("Hello, {}", name);
let mut name_upper = bool_type.name()?;
name_upper.to_mut().make_ascii_uppercase();
println!("Hello, {}", name_upper);
Ok(())
})
# }
```
After:
```rust
# #![allow(dead_code)]
# use pyo3::prelude::*;
# use pyo3::types::{PyBool};
# fn main() -> PyResult<()> {
Python::with_gil(|py| {
let bool_type = py.get_type_bound::<PyBool>();
let name = bool_type.name()?;
println!("Hello, {}", name);
// (if the full dotted path was desired, switch from `name()` to `fully_qualified_name()`)
let mut name_upper = bool_type.fully_qualified_name()?.to_string();
name_upper.make_ascii_uppercase();
println!("Hello, {}", name_upper);
Ok(())
})
# }
```
</details>
## from 0.20.* to 0.21
<details>
<summary><small>Click to expand</small></summary>
PyO3 0.21 introduces a new `Bound<'py, T>` smart pointer which replaces the existing "GIL Refs" API to interact with Python objects. For example, in PyO3 0.20 the reference `&'py PyAny` would be used to interact with Python objects. In PyO3 0.21 the updated type is `Bound<'py, PyAny>`. Making this change moves Rust ownership semantics out of PyO3's internals and into user code. This change fixes [a known soundness edge case of interaction with gevent](https://github.com/PyO3/pyo3/issues/3668) as well as improves CPU and [memory performance](https://github.com/PyO3/pyo3/issues/1056). For a full history of discussion see https://github.com/PyO3/pyo3/issues/3382.
The "GIL Ref" `&'py PyAny` and similar types such as `&'py PyDict` continue to be available as a deprecated API. Due to the advantages of the new API it is advised that all users make the effort to upgrade as soon as possible.
@ -22,7 +167,7 @@ The following sections are laid out in this order.
</details>
### Enable the `gil-refs` feature
<details open>
<details>
<summary><small>Click to expand</small></summary>
To make the transition for the PyO3 ecosystem away from the GIL Refs API as smooth as possible, in PyO3 0.21 no APIs consuming or producing GIL Refs have been altered. Instead, variants using `Bound<T>` smart pointers have been introduced, for example `PyTuple::new_bound` which returns `Bound<PyTuple>` is the replacement form of `PyTuple::new`. The GIL Ref APIs have been deprecated, but to make migration easier it is possible to disable these deprecation warnings by enabling the `gil-refs` feature.
@ -49,18 +194,18 @@ pyo3 = { version = "0.21", features = ["gil-refs"] }
</details>
### `PyTypeInfo` and `PyTryFrom` have been adjusted
<details open>
<details>
<summary><small>Click to expand</small></summary>
The `PyTryFrom` trait has aged poorly, its `try_from` method now conflicts with `TryFrom::try_from` in the 2021 edition prelude. A lot of its functionality was also duplicated with `PyTypeInfo`.
To tighten up the PyO3 traits as part of the deprecation of the GIL Refs API the `PyTypeInfo` trait has had a simpler companion `PyTypeCheck`. The methods [`PyAny::downcast`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html#method.downcast) and [`PyAny::downcast_exact`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html#method.downcast_exact) no longer use `PyTryFrom` as a bound, instead using `PyTypeCheck` and `PyTypeInfo` respectively.
To tighten up the PyO3 traits as part of the deprecation of the GIL Refs API the `PyTypeInfo` trait has had a simpler companion `PyTypeCheck`. The methods `PyAny::downcast` and `PyAny::downcast_exact` no longer use `PyTryFrom` as a bound, instead using `PyTypeCheck` and `PyTypeInfo` respectively.
To migrate, switch all type casts to use `obj.downcast()` instead of `try_from(obj)` (and similar for `downcast_exact`).
Before:
```rust
```rust,ignore
# #![allow(deprecated)]
# use pyo3::prelude::*;
# use pyo3::types::{PyInt, PyList};
@ -75,7 +220,7 @@ Python::with_gil(|py| {
After:
```rust
```rust,ignore
# use pyo3::prelude::*;
# use pyo3::types::{PyInt, PyList};
# fn main() -> PyResult<()> {
@ -92,15 +237,14 @@ Python::with_gil(|py| {
</details>
### `Iter(A)NextOutput` are deprecated
<details open>
<details>
<summary><small>Click to expand</small></summary>
The `__next__` and `__anext__` magic methods can now return any type convertible into Python objects directly just like all other `#[pymethods]`. The `IterNextOutput` used by `__next__` and `IterANextOutput` used by `__anext__` are subsequently deprecated. Most importantly, this change allows returning an awaitable from `__anext__` without non-sensically wrapping it into `Yield` or `Some`. Only the return types `Option<T>` and `Result<Option<T>, E>` are still handled in a special manner where `Some(val)` yields `val` and `None` stops iteration.
Starting with an implementation of a Python iterator using `IterNextOutput`, e.g.
```rust
#![allow(deprecated)]
```rust,ignore
use pyo3::prelude::*;
use pyo3::iter::IterNextOutput;
@ -214,21 +358,21 @@ impl PyClassAsyncIter {
</details>
### `PyType::name` has been renamed to `PyType::qualname`
<details open>
<details>
<summary><small>Click to expand</small></summary>
`PyType::name` has been renamed to `PyType::qualname` to indicate that it does indeed return the [qualified name](https://docs.python.org/3/glossary.html#term-qualified-name), matching the `__qualname__` attribute. The newly added `PyType::name` yields the full name including the module name now which corresponds to `__module__.__name__` on the level of attributes.
</details>
### `PyCell` has been deprecated
<details open>
<details>
<summary><small>Click to expand</small></summary>
Interactions with Python objects implemented in Rust no longer need to go though `PyCell<T>`. Instead iteractions with Python object now consistently go through `Bound<T>` or `Py<T>` independently of whether `T` is native Python object or a `#[pyclass]` implemented in Rust. Use `Bound::new` or `Py::new` respectively to create and `Bound::borrow(_mut)` / `Py::borrow(_mut)` to borrow the Rust object.
</details>
### Migrating from the GIL Refs API to `Bound<T>`
<details open>
<details>
<summary><small>Click to expand</small></summary>
To minimise breakage of code using the GIL Refs API, the `Bound<T>` smart pointer has been introduced by adding complements to all functions which accept or return GIL Refs. This allows code to migrate by replacing the deprecated APIs with the new ones.
@ -326,7 +470,7 @@ Despite a large amount of deprecations warnings produced by PyO3 to aid with the
</details>
### Deactivating the `gil-refs` feature
<details open>
<details>
<summary><small>Click to expand</small></summary>
As a final step of migration, deactivating the `gil-refs` feature will set up code for best performance and is intended to set up a forward-compatible API for PyO3 0.22.
@ -339,12 +483,12 @@ To make PyO3's core functionality continue to work while the GIL Refs API is in
PyO3 0.21 has introduced the [`PyBackedStr`]({{#PYO3_DOCS_URL}}/pyo3/pybacked/struct.PyBackedStr.html) and [`PyBackedBytes`]({{#PYO3_DOCS_URL}}/pyo3/pybacked/struct.PyBackedBytes.html) types to help with this case. The easiest way to avoid lifetime challenges from extracting `&str` is to use these. For more complex types like `Vec<&str>`, is now impossible to extract directly from a Python object and `Vec<PyBackedStr>` is the recommended upgrade path.
A key thing to note here is because extracting to these types now ties them to the input lifetime, some extremely common patterns may need to be split into multiple Rust lines. For example, the following snippet of calling `.extract::<&str>()` directly on the result of `.getattr()` needs to be adjusted when deactivating the `gil-refs-migration` feature.
A key thing to note here is because extracting to these types now ties them to the input lifetime, some extremely common patterns may need to be split into multiple Rust lines. For example, the following snippet of calling `.extract::<&str>()` directly on the result of `.getattr()` needs to be adjusted when deactivating the `gil-refs` feature.
Before:
```rust
# #[cfg(feature = "gil-refs-migration")] {
# #[cfg(feature = "gil-refs")] {
# use pyo3::prelude::*;
# use pyo3::types::{PyList, PyType};
# fn example<'py>(py: Python<'py>) -> PyResult<()> {
@ -393,6 +537,7 @@ assert_eq!(&*name, "list");
# }
# Python::with_gil(example).unwrap();
```
</details>
## from 0.19.* to 0.20
@ -641,7 +786,7 @@ drop(second);
The replacement is [`Python::with_gil`](https://docs.rs/pyo3/0.18.3/pyo3/marker/struct.Python.html#method.with_gil) which is more cumbersome but enforces the proper nesting by design, e.g.
```rust
```rust,ignore
# #![allow(dead_code)]
# use pyo3::prelude::*;
@ -666,7 +811,7 @@ let second = Python::with_gil(|py| Object::new(py));
drop(first);
drop(second);
// Or it ensure releasing the inner lock before the outer one.
// Or it ensures releasing the inner lock before the outer one.
Python::with_gil(|py| {
let first = Object::new(py);
let second = Python::with_gil(|py| Object::new(py));
@ -1089,7 +1234,7 @@ An additional advantage of using Rust's indexing conventions for these types is
that these types can now also support Rust's indexing operators as part of a
consistent API:
```rust
```rust,ignore
#![allow(deprecated)]
use pyo3::{Python, types::PyList};
@ -1565,7 +1710,7 @@ For more, see [the constructor section](class.md#constructor) of this guide.
<details>
<summary><small>Click to expand</small></summary>
PyO3 0.9 introduces [`PyCell`], which is a [`RefCell`]-like object wrapper
PyO3 0.9 introduces `PyCell`, which is a [`RefCell`]-like object wrapper
for ensuring Rust's rules regarding aliasing of references are upheld.
For more detail, see the
[Rust Book's section on Rust's rules of references](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#the-rules-of-references)
@ -1614,7 +1759,7 @@ However, for `#[pyproto]` and some functions, you need to manually fix the code.
In 0.8 object creation was done with `PyRef::new` and `PyRefMut::new`.
In 0.9 these have both been removed.
To upgrade code, please use
[`PyCell::new`]({{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyCell.html#method.new) instead.
`PyCell::new` instead.
If you need [`PyRef`] or [`PyRefMut`], just call `.borrow()` or `.borrow_mut()`
on the newly-created `PyCell`.
@ -1744,7 +1889,6 @@ impl PySequenceProtocol for ByteSequence {
[`FromPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.FromPyObject.html
[`PyAny`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html
[`PyCell`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyCell.html
[`PyBorrowMutError`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyBorrowMutError.html
[`PyRef`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html
[`PyRefMut`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html

View File

@ -13,8 +13,7 @@ fn double(x: usize) -> usize {
/// This module is implemented in Rust.
#[pymodule]
fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(double, m)?)?;
Ok(())
m.add_function(wrap_pyfunction!(double, m)?)
}
```
@ -32,11 +31,9 @@ fn double(x: usize) -> usize {
x * 2
}
#[pymodule]
#[pyo3(name = "custom_name")]
#[pymodule(name = "custom_name")]
fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(double, m)?)?;
Ok(())
m.add_function(wrap_pyfunction!(double, m)?)
}
```
@ -80,8 +77,7 @@ fn parent_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
fn register_child_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
let child_module = PyModule::new_bound(parent_module.py(), "child_module")?;
child_module.add_function(wrap_pyfunction!(func, &child_module)?)?;
parent_module.add_submodule(&child_module)?;
Ok(())
parent_module.add_submodule(&child_module)
}
#[pyfunction]
@ -106,14 +102,12 @@ submodules by using `from parent_module import child_module`. For more informati
It is not necessary to add `#[pymodule]` on nested modules, which is only required on the top-level module.
## Declarative modules (experimental)
## Declarative modules
Another syntax based on Rust inline modules is also available to declare modules.
The `experimental-declarative-modules` feature must be enabled to use it.
For example:
```rust
# #[cfg(feature = "experimental-declarative-modules")]
# mod declarative_module_test {
use pyo3::prelude::*;
@ -145,15 +139,42 @@ mod my_extension {
#[pymodule_init]
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
// Arbitrary code to run at the module initialization
m.add("double2", m.getattr("double")?)?;
Ok(())
m.add("double2", m.getattr("double")?)
}
}
# }
```
Some changes are planned to this feature before stabilization, like automatically
filling submodules into `sys.modules` to allow easier imports (see [issue #759](https://github.com/PyO3/pyo3/issues/759))
and filling the `module` argument of inlined `#[pyclass]` automatically with the proper module name.
Macro names might also change.
See [issue #3900](https://github.com/PyO3/pyo3/issues/3900) to track this feature progress.
The `#[pymodule]` macro automatically sets the `module` attribute of the `#[pyclass]` macros declared inside of it with its name.
For nested modules, the name of the parent module is automatically added.
In the following example, the `Unit` class will have for `module` `my_extension.submodule` because it is properly nested
but the `Ext` class will have for `module` the default `builtins` because it not nested.
```rust
# mod declarative_module_module_attr_test {
use pyo3::prelude::*;
#[pyclass]
struct Ext;
#[pymodule]
mod my_extension {
use super::*;
#[pymodule_export]
use super::Ext;
#[pymodule]
mod submodule {
use super::*;
// This is a submodule
#[pyclass] // This will be part of the module
struct Unit;
}
}
# }
```
It is possible to customize the `module` value for a `#[pymodule]` with the `#[pyo3(module = "MY_MODULE")]` option.
You can provide the `submodule` argument to `pymodule()` for modules that are not top-level modules -- it is automatically set for modules nested inside of a `#[pymodule]`.

View File

@ -1,6 +1,6 @@
# Parallelism
CPython has the infamous [Global Interpreter Lock](https://docs.python.org/3/glossary.html#term-global-interpreter-lock), which prevents several threads from executing Python bytecode in parallel. This makes threading in Python a bad fit for [CPU-bound](https://stackoverflow.com/questions/868568/) tasks and often forces developers to accept the overhead of multiprocessing.
CPython has the infamous [Global Interpreter Lock](https://docs.python.org/3/glossary.html#term-global-interpreter-lock), which prevents several threads from executing Python bytecode in parallel. This makes threading in Python a bad fit for [CPU-bound](https://en.wikipedia.org/wiki/CPU-bound) tasks and often forces developers to accept the overhead of multiprocessing.
In PyO3 parallelism can be easily achieved in Rust-only code. Let's take a look at our [word-count](https://github.com/PyO3/pyo3/blob/main/examples/word-count/src/lib.rs) example, where we have a `search` function that utilizes the [rayon](https://github.com/rayon-rs/rayon) crate to count words in parallel.
```rust,no_run

View File

@ -96,3 +96,47 @@ impl PartialEq<Foo> for FooBound<'_> {
}
}
```
## Disable the global reference pool
PyO3 uses global mutable state to keep track of deferred reference count updates implied by `impl<T> Drop for Py<T>` being called without the GIL being held. The necessary synchronization to obtain and apply these reference count updates when PyO3-based code next acquires the GIL is somewhat expensive and can become a significant part of the cost of crossing the Python-Rust boundary.
This functionality can be avoided by setting the `pyo3_disable_reference_pool` conditional compilation flag. This removes the global reference pool and the associated costs completely. However, it does _not_ remove the `Drop` implementation for `Py<T>` which is necessary to interoperate with existing Rust code written without PyO3-based code in mind. To stay compatible with the wider Rust ecosystem in these cases, we keep the implementation but abort when `Drop` is called without the GIL being held. If `pyo3_leak_on_drop_without_reference_pool` is additionally enabled, objects dropped without the GIL being held will be leaked instead which is always sound but might have determinal effects like resource exhaustion in the long term.
This limitation is important to keep in mind when this setting is used, especially when embedding Python code into a Rust application as it is quite easy to accidentally drop a `Py<T>` (or types containing it like `PyErr`, `PyBackedStr` or `PyBackedBytes`) returned from `Python::with_gil` without making sure to re-acquire the GIL beforehand. For example, the following code
```rust,ignore
# use pyo3::prelude::*;
# use pyo3::types::PyList;
let numbers: Py<PyList> = Python::with_gil(|py| PyList::empty_bound(py).unbind());
Python::with_gil(|py| {
numbers.bind(py).append(23).unwrap();
});
Python::with_gil(|py| {
numbers.bind(py).append(42).unwrap();
});
```
will abort if the list not explicitly disposed via
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyList;
let numbers: Py<PyList> = Python::with_gil(|py| PyList::empty_bound(py).unbind());
Python::with_gil(|py| {
numbers.bind(py).append(23).unwrap();
});
Python::with_gil(|py| {
numbers.bind(py).append(42).unwrap();
});
Python::with_gil(move |py| {
drop(numbers);
});
```
[conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html

View File

@ -2,9 +2,9 @@
If you already have some existing Python code that you need to execute from Rust, the following FAQs can help you select the right PyO3 functionality for your situation:
## Want to access Python APIs? Then use `PyModule::import`.
## Want to access Python APIs? Then use `PyModule::import_bound`.
[`Pymodule::import`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.import) can
[`PyModule::import_bound`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.import_bound) can
be used to get handle to a Python module from Rust. You can use this to import and use any Python
module available in your environment.
@ -24,9 +24,9 @@ fn main() -> PyResult<()> {
}
```
## Want to run just an expression? Then use `eval`.
## Want to run just an expression? Then use `eval_bound`.
[`Python::eval`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.eval) is
[`Python::eval_bound`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.eval_bound) is
a method to execute a [Python expression](https://docs.python.org/3.7/reference/expressions.html)
and return the evaluated value as a `Bound<'py, PyAny>` object.
@ -47,14 +47,14 @@ Python::with_gil(|py| {
# }
```
## Want to run statements? Then use `run`.
## Want to run statements? Then use `run_bound`.
[`Python::run`] is a method to execute one or more
[`Python::run_bound`] is a method to execute one or more
[Python statements](https://docs.python.org/3.7/reference/simple_stmts.html).
This method returns nothing (like any Python statement), but you can get
access to manipulated objects via the `locals` dict.
You can also use the [`py_run!`] macro, which is a shorthand for [`Python::run`].
You can also use the [`py_run!`] macro, which is a shorthand for [`Python::run_bound`].
Since [`py_run!`] panics on exceptions, we recommend you use this macro only for
quickly testing your Python extensions.
@ -95,9 +95,9 @@ assert userdata.as_tuple() == userdata_as_tuple
# }
```
## You have a Python file or code snippet? Then use `PyModule::from_code`.
## You have a Python file or code snippet? Then use `PyModule::from_code_bound`.
[`PyModule::from_code`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.from_code)
[`PyModule::from_code_bound`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.from_code_bound)
can be used to generate a Python module which can then be used just as if it was imported with
`PyModule::import`.
@ -171,7 +171,7 @@ fn main() -> PyResult<()> {
```
If `append_to_inittab` cannot be used due to constraints in the program,
an alternative is to create a module using [`PyModule::new`]
an alternative is to create a module using [`PyModule::new_bound`]
and insert it manually into `sys.modules`:
```rust
@ -394,4 +394,4 @@ Python::with_gil(|py| -> PyResult<()> {
```
[`PyModule::new`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.new
[`PyModule::new_bound`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.new_bound

View File

@ -107,7 +107,7 @@ fn main() -> PyResult<()> {
<div class="warning">
During PyO3's [migration from "GIL Refs" to the `Bound<T>` smart pointer](../migration.md#migrating-from-the-gil-refs-api-to-boundt), [`Py<T>::call`]({{#PYO3_DOCS_URL}}/pyo3/struct.Py.html#method.call) is temporarily named `call_bound` (and `call_method` is temporarily `call_method_bound`).
During PyO3's [migration from "GIL Refs" to the `Bound<T>` smart pointer](../migration.md#migrating-from-the-gil-refs-api-to-boundt), `Py<T>::call` is temporarily named [`Py<T>::call_bound`]({{#PYO3_DOCS_URL}}/pyo3/struct.Py.html#method.call_bound) (and `call_method` is temporarily `call_method_bound`).
(This temporary naming is only the case for the `Py<T>` smart pointer. The methods on the `&PyAny` GIL Ref such as `call` have not been given replacements, and the methods on the `Bound<PyAny>` smart pointer such as [`Bound<PyAny>::call`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call) already use follow the newest API conventions.)

View File

@ -1,6 +1,6 @@
# Using in Python a Rust function with trait bounds
PyO3 allows for easy conversion from Rust to Python for certain functions and classes (see the [conversion table](conversions/tables.md).
PyO3 allows for easy conversion from Rust to Python for certain functions and classes (see the [conversion table](conversions/tables.md)).
However, it is not always straightforward to convert Rust code that requires a given trait implementation as an argument.
This tutorial explains how to convert a Rust function that takes a trait as argument for use in Python with classes implementing the same methods as the trait.

View File

@ -2,7 +2,7 @@
PyO3 offers two main sets of types to interact with Python objects. This section of the guide expands into detail about these types and how to choose which to use.
The first set of types is are the [smart pointers][smart-pointers] which all Python objects are wrapped in. These are `Py<T>`, `Bound<'py, T>`, and `Borrowed<'a, 'py, T>`. The [first section below](#pyo3s-smart-pointers) expands on each of these in detail and why there are three of them.
The first set of types are the [smart pointers][smart-pointers] which all Python objects are wrapped in. These are `Py<T>`, `Bound<'py, T>`, and `Borrowed<'a, 'py, T>`. The [first section below](#pyo3s-smart-pointers) expands on each of these in detail and why there are three of them.
The second set of types are types which fill in the generic parameter `T` of the smart pointers. The most common is `PyAny`, which represents any Python object (similar to Python's `typing.Any`). There are also concrete types for many Python built-in types, such as `PyList`, `PyDict`, and `PyTuple`. User defined `#[pyclass]` types also fit this category. The [second section below](#concrete-python-types) expands on how to use these types.
@ -330,8 +330,10 @@ For a `&PyAny` object reference `any` where the underlying object is a Python-na
a list:
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# use pyo3::types::PyList;
# #[cfg(feature = "gil-refs")]
# Python::with_gil(|py| -> PyResult<()> {
#[allow(deprecated)] // PyList::empty is part of the deprecated "GIL Refs" API.
let obj: &PyAny = PyList::empty(py);
@ -351,8 +353,10 @@ let _: Py<PyList> = obj.extract()?;
For a `&PyAny` object reference `any` where the underlying object is a `#[pyclass]`:
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# #[pyclass] #[derive(Clone)] struct MyClass { }
# #[cfg(feature = "gil-refs")]
# Python::with_gil(|py| -> PyResult<()> {
#[allow(deprecated)] // into_ref is part of the deprecated GIL Refs API
let obj: &PyAny = Py::new(py, MyClass {})?.into_ref(py);
@ -390,8 +394,10 @@ To see all Python types exposed by `PyO3` consult the [`pyo3::types`][pyo3::type
**Conversions:**
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# use pyo3::types::PyList;
# #[cfg(feature = "gil-refs")]
# Python::with_gil(|py| -> PyResult<()> {
#[allow(deprecated)] // PyList::empty is part of the deprecated "GIL Refs" API.
let list = PyList::empty(py);
@ -440,8 +446,10 @@ Like PyO3's Python native types, the GIL Ref `&PyCell<T>` implements `Deref<Targ
`PyCell<T>` was used to access `&T` and `&mut T` via `PyRef<T>` and `PyRefMut<T>` respectively.
```rust
#![allow(unused_imports)]
# use pyo3::prelude::*;
# #[pyclass] struct MyClass { }
# #[cfg(feature = "gil-refs")]
# Python::with_gil(|py| -> PyResult<()> {
#[allow(deprecated)] // &PyCell is part of the deprecated GIL Refs API
let cell: &PyCell<MyClass> = PyCell::new(py, MyClass {})?;
@ -461,8 +469,10 @@ let _: &mut MyClass = &mut *py_ref_mut;
`PyCell<T>` was also accessed like a Python-native type.
```rust
#![allow(unused_imports)]
# use pyo3::prelude::*;
# #[pyclass] struct MyClass { }
# #[cfg(feature = "gil-refs")]
# Python::with_gil(|py| -> PyResult<()> {
#[allow(deprecated)] // &PyCell is part of the deprecate GIL Refs API
let cell: &PyCell<MyClass> = PyCell::new(py, MyClass {})?;

View File

@ -0,0 +1 @@
Nested declarative `#[pymodule]` are automatically treated as submodules (no `PyInit_` entrypoint is created)

View File

@ -0,0 +1 @@
Deprecate `PyAnyMethods::is_ellipsis` (`Py::is_ellpsis` was deprecated in PyO3 0.20).

View File

@ -0,0 +1 @@
Remove all functionality deprecated in PyO3 0.20.

View File

@ -0,0 +1 @@
Remove all functionality deprecated in PyO3 0.21.

View File

@ -0,0 +1,3 @@
This PR lets PyO3 checks `Py_GIL_DISABLED` build flag and prevents `pyo3-ffi` crate building against GIL-less Python,
unless
explicitly opt using the `UNSAFE_PYO3_BUILD_FREE_THREADED` environment flag.

View File

@ -0,0 +1 @@
Fix compile failure in declarative `#[pymodule]` under presence of `#![no_implicit_prelude]`.

View File

@ -0,0 +1 @@
`#[pymodule(...)]` now directly accepts all relevant `#[pyo3(...)]` options.

View File

@ -9,7 +9,7 @@ import tempfile
from functools import lru_cache
from glob import glob
from pathlib import Path
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple
import nox
import nox.command
@ -30,7 +30,7 @@ PYO3_TARGET = Path(os.environ.get("CARGO_TARGET_DIR", PYO3_DIR / "target")).abso
PYO3_GUIDE_SRC = PYO3_DIR / "guide" / "src"
PYO3_GUIDE_TARGET = PYO3_TARGET / "guide"
PYO3_DOCS_TARGET = PYO3_TARGET / "doc"
PY_VERSIONS = ("3.7", "3.8", "3.9", "3.10", "3.11", "3.12")
PY_VERSIONS = ("3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13")
PYPY_VERSIONS = ("3.7", "3.8", "3.9", "3.10")
@ -394,8 +394,17 @@ def check_guide(session: nox.Session):
docs(session)
session.posargs.extend(posargs)
if toml is None:
session.error("requires Python 3.11 or `toml` to be installed")
pyo3_version = toml.loads((PYO3_DIR / "Cargo.toml").read_text())["package"][
"version"
]
remaps = {
f"file://{PYO3_GUIDE_SRC}/([^/]*/)*?%7B%7B#PYO3_DOCS_URL}}}}": f"file://{PYO3_DOCS_TARGET}",
f"https://pyo3.rs/v{pyo3_version}": f"file://{PYO3_GUIDE_TARGET}",
"https://pyo3.rs/main/": f"file://{PYO3_GUIDE_TARGET}/",
"https://pyo3.rs/latest/": f"file://{PYO3_GUIDE_TARGET}/",
"%7B%7B#PYO3_DOCS_VERSION}}": "latest",
}
remap_args = []
@ -415,10 +424,10 @@ def check_guide(session: nox.Session):
_run(
session,
"lychee",
PYO3_DOCS_TARGET,
f"--remap=https://pyo3.rs/main/ file://{PYO3_GUIDE_TARGET}/",
f"--remap=https://pyo3.rs/latest/ file://{PYO3_GUIDE_TARGET}/",
str(PYO3_DOCS_TARGET),
*remap_args,
f"--exclude=file://{PYO3_DOCS_TARGET}",
"--exclude=http://www.adobe.com/",
*session.posargs,
)
@ -542,8 +551,8 @@ def check_changelog(session: nox.Session):
print(fragment.name)
@nox.session(name="set-minimal-package-versions", venv_backend="none")
def set_minimal_package_versions(session: nox.Session):
@nox.session(name="set-msrv-package-versions", venv_backend="none")
def set_msrv_package_versions(session: nox.Session):
from collections import defaultdict
if toml is None:
@ -557,22 +566,11 @@ def set_minimal_package_versions(session: nox.Session):
"examples/word-count",
)
min_pkg_versions = {
"rust_decimal": "1.26.1",
"csv": "1.1.6",
"indexmap": "1.6.2",
"hashbrown": "0.9.1",
"log": "0.4.17",
"once_cell": "1.17.2",
"rayon": "1.6.1",
"rayon-core": "1.10.2",
"regex": "1.7.3",
"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",
"regex": "1.9.6",
"proptest": "1.2.0",
"trybuild": "1.0.89",
"eyre": "0.6.8",
"allocator-api2": "0.2.10",
}
# run cargo update first to ensure that everything is at highest
@ -641,11 +639,11 @@ def test_version_limits(session: nox.Session):
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")
assert "3.14" not in PY_VERSIONS
config_file.set("CPython", "3.14")
_run_cargo(session, "check", env=env, expect_error=True)
# 3.13 CPython should build with forward compatibility
# 3.14 CPython should build with forward compatibility
env["PYO3_USE_ABI3_FORWARD_COMPATIBILITY"] = "1"
_run_cargo(session, "check", env=env)
@ -657,6 +655,14 @@ def test_version_limits(session: nox.Session):
config_file.set("PyPy", "3.11")
_run_cargo(session, "check", env=env, expect_error=True)
# Python build with GIL disabled should fail building
config_file.set("CPython", "3.13", build_flags=["Py_GIL_DISABLED"])
_run_cargo(session, "check", env=env, expect_error=True)
# Python build with GIL disabled should pass with env flag on
env["UNSAFE_PYO3_BUILD_FREE_THREADED"] = "1"
_run_cargo(session, "check", env=env)
@nox.session(name="check-feature-powerset", venv_backend="none")
def check_feature_powerset(session: nox.Session):
@ -673,7 +679,7 @@ def check_feature_powerset(session: nox.Session):
"default",
"auto-initialize",
"generate-import-lib",
"multiple-pymethods", # TODO add this after MSRV 1.62
"multiple-pymethods", # Because it's not supported on wasm
}
features = cargo_toml["features"]
@ -718,10 +724,14 @@ def check_feature_powerset(session: nox.Session):
rust_flags = env.get("RUSTFLAGS", "")
env["RUSTFLAGS"] = f"{rust_flags} -Dwarnings"
subcommand = "hack"
if "minimal-versions" in session.posargs:
subcommand = "minimal-versions"
comma_join = ",".join
_run_cargo(
session,
"hack",
subcommand,
"--feature-powerset",
'--optional-deps=""',
f'--skip="{comma_join(features_to_skip)}"',
@ -744,7 +754,9 @@ def update_ui_tests(session: nox.Session):
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")
env = os.environ.copy()
env["PYO3_PYTHON"] = sys.executable
_run_cargo(session, "doc", _FFI_CHECK, "-p", "pyo3-ffi", "--no-deps", env=env)
@lru_cache()
@ -772,10 +784,9 @@ def _get_rust_default_target() -> str:
@lru_cache()
def _get_feature_sets() -> Tuple[Tuple[str, ...], ...]:
"""Returns feature sets to use for clippy job"""
rust_version = _get_rust_version()
cargo_target = os.getenv("CARGO_BUILD_TARGET", "")
if rust_version[:2] >= (1, 62) and "wasm32-wasi" not in cargo_target:
# multiple-pymethods feature not supported before 1.62 or on WASI
if "wasm32-wasi" not in cargo_target:
# multiple-pymethods not supported on wasm
return (
("--no-default-features",),
(
@ -916,7 +927,9 @@ class _ConfigFile:
def __init__(self, config_file) -> None:
self._config_file = config_file
def set(self, implementation: str, version: str) -> None:
def set(
self, implementation: str, version: str, build_flags: Iterable[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)
@ -924,6 +937,7 @@ class _ConfigFile:
f"""\
implementation={implementation}
version={version}
build_flags={','.join(build_flags)}
suppress_build_script_link_lines=true
"""
)

View File

@ -1,4 +1,4 @@
use codspeed_criterion_compat::{criterion_group, criterion_main, BatchSize, Bencher, Criterion};
use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion};
use pyo3::prelude::*;
@ -9,14 +9,8 @@ fn bench_clean_acquire_gil(b: &mut Bencher<'_>) {
fn bench_dirty_acquire_gil(b: &mut Bencher<'_>) {
let obj = Python::with_gil(|py| py.None());
b.iter_batched(
|| {
// Clone and drop an object so that the GILPool has work to do.
let _ = obj.clone();
},
|_| Python::with_gil(|_| {}),
BatchSize::NumBatches(1),
);
// Drop the returned clone of the object so that the reference pool has work to do.
b.iter(|| Python::with_gil(|py| obj.clone_ref(py)));
}
fn criterion_benchmark(c: &mut Criterion) {

View File

@ -1,4 +1,12 @@
use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion};
use codspeed_criterion_compat::{criterion_group, criterion_main, BatchSize, Bencher, Criterion};
use std::sync::{
atomic::{AtomicUsize, Ordering},
mpsc::channel,
Arc, Barrier,
};
use std::thread::spawn;
use std::time::{Duration, Instant};
use pyo3::prelude::*;
@ -6,14 +14,108 @@ fn drop_many_objects(b: &mut Bencher<'_>) {
Python::with_gil(|py| {
b.iter(|| {
for _ in 0..1000 {
std::mem::drop(py.None());
drop(py.None());
}
});
});
}
fn drop_many_objects_without_gil(b: &mut Bencher<'_>) {
b.iter_batched(
|| {
Python::with_gil(|py| {
(0..1000)
.map(|_| py.None().into_py(py))
.collect::<Vec<PyObject>>()
})
},
|objs| {
drop(objs);
Python::with_gil(|_py| ());
},
BatchSize::SmallInput,
);
}
fn drop_many_objects_multiple_threads(b: &mut Bencher<'_>) {
const THREADS: usize = 5;
let barrier = Arc::new(Barrier::new(1 + THREADS));
let done = Arc::new(AtomicUsize::new(0));
let sender = (0..THREADS)
.map(|_| {
let (sender, receiver) = channel();
let barrier = barrier.clone();
let done = done.clone();
spawn(move || {
for objs in receiver {
barrier.wait();
drop(objs);
done.fetch_add(1, Ordering::AcqRel);
}
});
sender
})
.collect::<Vec<_>>();
b.iter_custom(|iters| {
let mut duration = Duration::ZERO;
let mut last_done = done.load(Ordering::Acquire);
for _ in 0..iters {
for sender in &sender {
let objs = Python::with_gil(|py| {
(0..1000 / THREADS)
.map(|_| py.None().into_py(py))
.collect::<Vec<PyObject>>()
});
sender.send(objs).unwrap();
}
barrier.wait();
let start = Instant::now();
loop {
Python::with_gil(|_py| ());
let done = done.load(Ordering::Acquire);
if done - last_done == THREADS {
last_done = done;
break;
}
}
Python::with_gil(|_py| ());
duration += start.elapsed();
}
duration
});
}
fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("drop_many_objects", drop_many_objects);
c.bench_function(
"drop_many_objects_without_gil",
drop_many_objects_without_gil,
);
c.bench_function(
"drop_many_objects_multiple_threads",
drop_many_objects_multiple_threads,
);
}
criterion_group!(benches, criterion_benchmark);

View File

@ -1,6 +1,6 @@
[package]
name = "pyo3-build-config"
version = "0.21.1"
version = "0.23.0-dev"
description = "Build configuration for the PyO3 ecosystem"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"]
@ -13,11 +13,11 @@ edition = "2021"
[dependencies]
once_cell = "1"
python3-dll-a = { version = "0.2.6", optional = true }
target-lexicon = "0.12"
target-lexicon = "0.12.14"
[build-dependencies]
python3-dll-a = { version = "0.2.6", optional = true }
target-lexicon = "0.12"
target-lexicon = "0.12.14"
[features]
default = []

View File

@ -26,11 +26,11 @@ use target_lexicon::{Environment, OperatingSystem};
use crate::{
bail, ensure,
errors::{Context, Error, Result},
format_warn, warn,
warn,
};
/// Minimum Python version PyO3 supports.
const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 7 };
pub(crate) const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 7 };
/// GraalPy may implement the same CPython version over multiple releases.
const MINIMUM_SUPPORTED_VERSION_GRAALPY: PythonVersion = PythonVersion {
@ -39,7 +39,7 @@ const MINIMUM_SUPPORTED_VERSION_GRAALPY: PythonVersion = PythonVersion {
};
/// Maximum Python version that can be used as minimum required Python version with abi3.
const ABI3_MAX_MINOR: u8 = 12;
pub(crate) const ABI3_MAX_MINOR: u8 = 12;
/// Gets an environment variable owned by cargo.
///
@ -171,20 +171,13 @@ impl InterpreterConfig {
out.push(format!("cargo:rustc-cfg=Py_3_{}", i));
}
if self.implementation.is_pypy() {
out.push("cargo:rustc-cfg=PyPy".to_owned());
if self.abi3 {
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."
));
match self.implementation {
PythonImplementation::CPython => {}
PythonImplementation::PyPy => out.push("cargo:rustc-cfg=PyPy".to_owned()),
PythonImplementation::GraalPy => out.push("cargo:rustc-cfg=GraalPy".to_owned()),
}
} else if self.implementation.is_graalpy() {
println!("cargo:rustc-cfg=GraalPy");
if self.abi3 {
warn!("GraalPy does not support abi3 so the build artifacts will be version-specific.");
}
} else if self.abi3 {
out.push("cargo:rustc-cfg=Py_LIMITED_API".to_owned());
}
@ -775,6 +768,8 @@ pub fn is_linking_libpython() -> bool {
/// Must be called from a PyO3 crate build script.
fn is_linking_libpython_for_target(target: &Triple) -> bool {
target.operating_system == OperatingSystem::Windows
// See https://github.com/PyO3/pyo3/issues/4068#issuecomment-2051159852
|| target.operating_system == OperatingSystem::Aix
|| target.environment == Environment::Android
|| target.environment == Environment::Androideabi
|| !is_extension_module()
@ -1001,6 +996,7 @@ pub enum BuildFlag {
Py_DEBUG,
Py_REF_DEBUG,
Py_TRACE_REFS,
Py_GIL_DISABLED,
COUNT_ALLOCS,
Other(String),
}
@ -1021,6 +1017,7 @@ impl FromStr for BuildFlag {
"Py_DEBUG" => Ok(BuildFlag::Py_DEBUG),
"Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG),
"Py_TRACE_REFS" => Ok(BuildFlag::Py_TRACE_REFS),
"Py_GIL_DISABLED" => Ok(BuildFlag::Py_GIL_DISABLED),
"COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS),
other => Ok(BuildFlag::Other(other.to_owned())),
}
@ -1044,10 +1041,11 @@ impl FromStr for BuildFlag {
pub struct BuildFlags(pub HashSet<BuildFlag>);
impl BuildFlags {
const ALL: [BuildFlag; 4] = [
const ALL: [BuildFlag; 5] = [
BuildFlag::Py_DEBUG,
BuildFlag::Py_REF_DEBUG,
BuildFlag::Py_TRACE_REFS,
BuildFlag::Py_GIL_DISABLED,
BuildFlag::COUNT_ALLOCS,
];
@ -1061,7 +1059,7 @@ impl BuildFlags {
.iter()
.filter(|flag| {
config_map
.get_value(&flag.to_string())
.get_value(flag.to_string())
.map_or(false, |value| value == "1")
})
.cloned()
@ -1211,7 +1209,7 @@ fn ends_with(entry: &DirEntry, pat: &str) -> bool {
/// Returns `None` if the library directory is not available, and a runtime error
/// when no or multiple sysconfigdata files are found.
fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result<Option<PathBuf>> {
let mut sysconfig_paths = find_all_sysconfigdata(cross);
let mut sysconfig_paths = find_all_sysconfigdata(cross)?;
if sysconfig_paths.is_empty() {
if let Some(lib_dir) = cross.lib_dir.as_ref() {
bail!("Could not find _sysconfigdata*.py in {}", lib_dir.display());
@ -1274,11 +1272,16 @@ fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result<Option<PathBuf>> {
///
/// Returns an empty vector when the target Python library directory
/// is not set via `PYO3_CROSS_LIB_DIR`.
pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Vec<PathBuf> {
pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
let sysconfig_paths = if let Some(lib_dir) = cross.lib_dir.as_ref() {
search_lib_dir(lib_dir, cross)
search_lib_dir(lib_dir, cross).with_context(|| {
format!(
"failed to search the lib dir at 'PYO3_CROSS_LIB_DIR={}'",
lib_dir.display()
)
})?
} else {
return Vec::new();
return Ok(Vec::new());
};
let sysconfig_name = env_var("_PYTHON_SYSCONFIGDATA_NAME");
@ -1296,7 +1299,7 @@ pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Vec<PathBuf> {
sysconfig_paths.sort();
sysconfig_paths.dedup();
sysconfig_paths
Ok(sysconfig_paths)
}
fn is_pypy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
@ -1327,9 +1330,14 @@ fn is_cpython_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
}
/// recursive search for _sysconfigdata, returns all possibilities of sysconfigdata paths
fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<PathBuf> {
fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
let mut sysconfig_paths = vec![];
for f in fs::read_dir(path).expect("Path does not exist") {
for f in fs::read_dir(path.as_ref()).with_context(|| {
format!(
"failed to list the entries in '{}'",
path.as_ref().display()
)
})? {
sysconfig_paths.extend(match &f {
// Python 3.7+ sysconfigdata with platform specifics
Ok(f) if starts_with(f, "_sysconfigdata_") && ends_with(f, "py") => vec![f.path()],
@ -1337,7 +1345,7 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
let file_name = f.file_name();
let file_name = file_name.to_string_lossy();
if file_name == "build" || file_name == "lib" {
search_lib_dir(f.path(), cross)
search_lib_dir(f.path(), cross)?
} else if file_name.starts_with("lib.") {
// check if right target os
if !file_name.contains(&cross.target.operating_system.to_string()) {
@ -1347,12 +1355,12 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
if !file_name.contains(&cross.target.architecture.to_string()) {
continue;
}
search_lib_dir(f.path(), cross)
search_lib_dir(f.path(), cross)?
} else if is_cpython_lib_dir(&file_name, &cross.version)
|| is_pypy_lib_dir(&file_name, &cross.version)
|| is_graalpy_lib_dir(&file_name, &cross.version)
{
search_lib_dir(f.path(), cross)
search_lib_dir(f.path(), cross)?
} else {
continue;
}
@ -1381,7 +1389,7 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
}
}
sysconfig_paths
Ok(sysconfig_paths)
}
/// Find cross compilation information from sysconfigdata file
@ -2720,10 +2728,7 @@ mod tests {
"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(),
"cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
]
);
}
@ -2755,4 +2760,24 @@ mod tests {
]
);
}
#[test]
fn test_find_sysconfigdata_in_invalid_lib_dir() {
let e = find_all_sysconfigdata(&CrossCompileConfig {
lib_dir: Some(PathBuf::from("/abc/123/not/a/real/path")),
version: None,
implementation: None,
target: triple!("x86_64-unknown-linux-gnu"),
})
.unwrap_err();
// actual error message is platform-dependent, so just check the context we add
assert!(e.report().to_string().starts_with(
"failed to search the lib dir at 'PYO3_CROSS_LIB_DIR=/abc/123/not/a/real/path'\n\
caused by:\n \
- 0: failed to list the entries in '/abc/123/not/a/real/path'\n \
- 1: \
"
));
}
}

View File

@ -18,7 +18,6 @@ use std::{
use std::{env, process::Command, str::FromStr};
#[cfg(feature = "resolve-config")]
use once_cell::sync::OnceCell;
pub use impl_::{
@ -40,9 +39,12 @@ use target_lexicon::OperatingSystem;
/// | `#[cfg(PyPy)]` | This marks code which is run when compiling for PyPy. |
/// | `#[cfg(GraalPy)]` | This marks code which is run when compiling for GraalPy. |
///
/// For examples of how to use these attributes, [see PyO3's guide](https://pyo3.rs/latest/building-and-distribution/multiple_python_versions.html).
/// For examples of how to use these attributes,
#[doc = concat!("[see PyO3's guide](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution/multiple_python_versions.html)")]
/// .
#[cfg(feature = "resolve-config")]
pub fn use_pyo3_cfgs() {
print_expected_cfgs();
for cargo_command in get().build_script_outputs() {
println!("{}", cargo_command)
}
@ -86,6 +88,8 @@ pub fn get() -> &'static InterpreterConfig {
.map(|path| path.exists())
.unwrap_or(false);
// CONFIG_FILE is generated in build.rs, so it's content can vary
#[allow(unknown_lints, clippy::const_is_empty)]
if let Some(interpreter_config) = InterpreterConfig::from_cargo_dep_env() {
interpreter_config
} else if !CONFIG_FILE.is_empty() {
@ -132,28 +136,50 @@ fn resolve_cross_compile_config_path() -> Option<PathBuf> {
/// so this function is unstable.
#[doc(hidden)]
pub fn print_feature_cfgs() {
fn rustc_minor_version() -> Option<u32> {
let rustc = env::var_os("RUSTC")?;
let output = Command::new(rustc).arg("--version").output().ok()?;
let version = core::str::from_utf8(&output.stdout).ok()?;
let mut pieces = version.split('.');
if pieces.next() != Some("rustc 1") {
return None;
}
pieces.next()?.parse().ok()
}
let rustc_minor_version = rustc_minor_version().unwrap_or(0);
// Enable use of const initializer for thread_local! on Rust 1.59 and greater
if rustc_minor_version >= 59 {
println!("cargo:rustc-cfg=thread_local_const_init");
}
// invalid_from_utf8 lint was added in Rust 1.74
if rustc_minor_version >= 74 {
println!("cargo:rustc-cfg=invalid_from_utf8_lint");
}
if rustc_minor_version >= 77 {
println!("cargo:rustc-cfg=c_str_lit");
}
// Actually this is available on 1.78, but we should avoid
// https://github.com/rust-lang/rust/issues/124651 just in case
if rustc_minor_version >= 79 {
println!("cargo:rustc-cfg=diagnostic_namespace");
}
}
/// Registers `pyo3`s config names as reachable cfg expressions
///
/// - <https://github.com/rust-lang/cargo/pull/13571>
/// - <https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#rustc-check-cfg>
#[doc(hidden)]
pub fn print_expected_cfgs() {
if rustc_minor_version().map_or(false, |version| version < 80) {
// rustc 1.80.0 stabilized `rustc-check-cfg` feature, don't emit before
return;
}
println!("cargo:rustc-check-cfg=cfg(Py_LIMITED_API)");
println!("cargo:rustc-check-cfg=cfg(PyPy)");
println!("cargo:rustc-check-cfg=cfg(GraalPy)");
println!("cargo:rustc-check-cfg=cfg(py_sys_config, values(\"Py_DEBUG\", \"Py_REF_DEBUG\", \"Py_TRACE_REFS\", \"COUNT_ALLOCS\"))");
println!("cargo:rustc-check-cfg=cfg(invalid_from_utf8_lint)");
println!("cargo:rustc-check-cfg=cfg(pyo3_disable_reference_pool)");
println!("cargo:rustc-check-cfg=cfg(pyo3_leak_on_drop_without_reference_pool)");
println!("cargo:rustc-check-cfg=cfg(diagnostic_namespace)");
println!("cargo:rustc-check-cfg=cfg(c_str_lit)");
// allow `Py_3_*` cfgs from the minimum supported version up to the
// maximum minor version (+1 for development for the next)
for i in impl_::MINIMUM_SUPPORTED_VERSION.minor..=impl_::ABI3_MAX_MINOR + 1 {
println!("cargo:rustc-check-cfg=cfg(Py_3_{i})");
}
}
/// Private exports used in PyO3's build.rs
@ -182,6 +208,8 @@ pub mod pyo3_build_script_impl {
/// correct value for CARGO_CFG_TARGET_OS).
#[cfg(feature = "resolve-config")]
pub fn resolve_interpreter_config() -> Result<InterpreterConfig> {
// CONFIG_FILE is generated in build.rs, so it's content can vary
#[allow(unknown_lints, clippy::const_is_empty)]
if !CONFIG_FILE.is_empty() {
let mut interperter_config = InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE))?;
interperter_config.generate_import_libs()?;
@ -212,6 +240,20 @@ pub mod pyo3_build_script_impl {
}
}
fn rustc_minor_version() -> Option<u32> {
static RUSTC_MINOR_VERSION: OnceCell<Option<u32>> = OnceCell::new();
*RUSTC_MINOR_VERSION.get_or_init(|| {
let rustc = env::var_os("RUSTC")?;
let output = Command::new(rustc).arg("--version").output().ok()?;
let version = core::str::from_utf8(&output.stdout).ok()?;
let mut pieces = version.split('.');
if pieces.next() != Some("rustc 1") {
return None;
}
pieces.next()?.parse().ok()
})
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -10,6 +10,6 @@ proc-macro = true
[dependencies]
glob = "0.3"
quote = "1"
proc-macro2 = "1"
proc-macro2 = "1.0.60"
scraper = "0.17"
pyo3-build-config = { path = "../../pyo3-build-config" }

View File

@ -1,6 +1,6 @@
[package]
name = "pyo3-ffi"
version = "0.21.1"
version = "0.23.0-dev"
description = "Python-API bindings for the PyO3 ecosystem"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"]
@ -38,7 +38,7 @@ abi3-py312 = ["abi3", "pyo3-build-config/abi3-py312"]
generate-import-lib = ["pyo3-build-config/python3-dll-a"]
[build-dependencies]
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.21.1", features = ["resolve-config"] }
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.0-dev", features = ["resolve-config"] }
[lints]
workspace = true

View File

@ -14,7 +14,7 @@ Manual][capi] for up-to-date documentation.
PyO3 supports the following software versions:
- Python 3.7 and up (CPython and PyPy)
- Rust 1.56 and up
- Rust 1.63 and up
# Example: Building Python Native modules
@ -51,10 +51,8 @@ use pyo3_ffi::*;
static mut MODULE_DEF: PyModuleDef = PyModuleDef {
m_base: PyModuleDef_HEAD_INIT,
m_name: "string_sum\0".as_ptr().cast::<c_char>(),
m_doc: "A Python module written in Rust.\0"
.as_ptr()
.cast::<c_char>(),
m_name: c_str!("string_sum").as_ptr(),
m_doc: c_str!("A Python module written in Rust.").as_ptr(),
m_size: 0,
m_methods: unsafe { METHODS.as_mut_ptr().cast() },
m_slots: std::ptr::null_mut(),
@ -65,14 +63,12 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef {
static mut METHODS: [PyMethodDef; 2] = [
PyMethodDef {
ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(),
ml_name: c_str!("sum_as_string").as_ptr(),
ml_meth: PyMethodDefPointer {
_PyCFunctionFast: sum_as_string,
},
ml_flags: METH_FASTCALL,
ml_doc: "returns the sum of two integers as a string\0"
.as_ptr()
.cast::<c_char>(),
ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
},
// A zeroed PyMethodDef to mark the end of the array.
PyMethodDef::zeroed()
@ -93,9 +89,7 @@ pub unsafe extern "C" fn sum_as_string(
if nargs != 2 {
PyErr_SetString(
PyExc_TypeError,
"sum_as_string() expected 2 positional arguments\0"
.as_ptr()
.cast::<c_char>(),
c_str!("sum_as_string() expected 2 positional arguments").as_ptr(),
);
return std::ptr::null_mut();
}
@ -104,9 +98,7 @@ pub unsafe extern "C" fn sum_as_string(
if PyLong_Check(arg1) == 0 {
PyErr_SetString(
PyExc_TypeError,
"sum_as_string() expected an int for positional argument 1\0"
.as_ptr()
.cast::<c_char>(),
c_str!("sum_as_string() expected an int for positional argument 1").as_ptr(),
);
return std::ptr::null_mut();
}
@ -120,9 +112,7 @@ pub unsafe extern "C" fn sum_as_string(
if PyLong_Check(arg2) == 0 {
PyErr_SetString(
PyExc_TypeError,
"sum_as_string() expected an int for positional argument 2\0"
.as_ptr()
.cast::<c_char>(),
c_str!("sum_as_string() expected an int for positional argument 2").as_ptr(),
);
return std::ptr::null_mut();
}
@ -140,7 +130,7 @@ pub unsafe extern "C" fn sum_as_string(
None => {
PyErr_SetString(
PyExc_OverflowError,
"arguments too large to add\0".as_ptr().cast::<c_char>(),
c_str!("arguments too large to add").as_ptr(),
);
std::ptr::null_mut()
}

View File

@ -4,8 +4,9 @@ use pyo3_build_config::{
cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config,
InterpreterConfig, PythonVersion,
},
PythonImplementation,
warn, BuildFlag, PythonImplementation,
};
use std::ops::Not;
/// Minimum Python version PyO3 supports.
struct SupportedVersions {
@ -17,7 +18,7 @@ const SUPPORTED_VERSIONS_CPYTHON: SupportedVersions = SupportedVersions {
min: PythonVersion { major: 3, minor: 7 },
max: PythonVersion {
major: 3,
minor: 12,
minor: 13,
},
};
@ -104,6 +105,37 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> {
}
}
if interpreter_config.abi3 {
match interpreter_config.implementation {
PythonImplementation::CPython => {}
PythonImplementation::PyPy => 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."
),
PythonImplementation::GraalPy => warn!(
"GraalPy does not support abi3 so the build artifacts will be version-specific."
),
}
}
Ok(())
}
fn ensure_gil_enabled(interpreter_config: &InterpreterConfig) -> Result<()> {
let gil_enabled = interpreter_config
.build_flags
.0
.contains(&BuildFlag::Py_GIL_DISABLED)
.not();
ensure!(
gil_enabled || std::env::var("UNSAFE_PYO3_BUILD_FREE_THREADED").map_or(false, |os_str| os_str == "1"),
"the Python interpreter was built with the GIL disabled, which is not yet supported by PyO3\n\
= help: see https://github.com/PyO3/pyo3/issues/4265 for more information\n\
= help: please check if an updated version of PyO3 is available. Current version: {}\n\
= help: set UNSAFE_PYO3_BUILD_FREE_THREADED=1 to suppress this check and build anyway for free-threaded Python",
std::env::var("CARGO_PKG_VERSION").unwrap()
);
Ok(())
}
@ -172,6 +204,7 @@ fn configure_pyo3() -> Result<()> {
ensure_python_version(&interpreter_config)?;
ensure_target_pointer_width(&interpreter_config)?;
ensure_gil_enabled(&interpreter_config)?;
// Serialize the whole interpreter config into DEP_PYTHON_PYO3_CONFIG env var.
interpreter_config.to_cargo_dep_env()?;
@ -189,7 +222,7 @@ fn configure_pyo3() -> Result<()> {
println!("{}", line);
}
// Emit cfgs like `thread_local_const_init`
// Emit cfgs like `invalid_from_utf8_lint`
print_feature_cfgs();
Ok(())
@ -205,6 +238,7 @@ fn print_config_and_exit(config: &InterpreterConfig) {
}
fn main() {
pyo3_build_config::print_expected_cfgs();
if let Err(e) = configure_pyo3() {
eprintln!("error: {}", e.report());
std::process::exit(1)

View File

@ -114,10 +114,7 @@ extern "C" {
#[cfg(not(any(Py_3_8, PyPy)))]
#[inline]
pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int {
crate::PyObject_HasAttrString(
crate::Py_TYPE(o).cast(),
"__next__\0".as_ptr() as *const c_char,
)
crate::PyObject_HasAttrString(crate::Py_TYPE(o).cast(), c_str!("__next__").as_ptr())
}
extern "C" {

View File

@ -4,8 +4,6 @@ use std::os::raw::{c_char, c_int};
#[cfg(not(Py_3_11))]
use crate::Py_buffer;
#[cfg(Py_3_8)]
use crate::pyport::PY_SSIZE_T_MAX;
#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))]
use crate::{
vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET, PyTuple_Check,
@ -42,14 +40,14 @@ extern "C" {
}
#[cfg(Py_3_8)]
const PY_VECTORCALL_ARGUMENTS_OFFSET: Py_ssize_t =
1 << (8 * std::mem::size_of::<Py_ssize_t>() as Py_ssize_t - 1);
const PY_VECTORCALL_ARGUMENTS_OFFSET: size_t =
1 << (8 * std::mem::size_of::<size_t>() as size_t - 1);
#[cfg(Py_3_8)]
#[inline(always)]
pub unsafe fn PyVectorcall_NARGS(n: size_t) -> Py_ssize_t {
assert!(n <= (PY_SSIZE_T_MAX as size_t));
(n as Py_ssize_t) & !PY_VECTORCALL_ARGUMENTS_OFFSET
let n = n & !PY_VECTORCALL_ARGUMENTS_OFFSET;
n.try_into().expect("cannot fail due to mask")
}
#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))]
@ -63,7 +61,7 @@ pub unsafe fn PyVectorcall_Function(callable: *mut PyObject) -> Option<vectorcal
assert!(PyCallable_Check(callable) > 0);
let offset = (*tp).tp_vectorcall_offset;
assert!(offset > 0);
let ptr = (callable as *const c_char).offset(offset) as *const Option<vectorcallfunc>;
let ptr = callable.cast::<c_char>().offset(offset).cast();
*ptr
}
@ -184,7 +182,7 @@ pub unsafe fn PyObject_CallOneArg(func: *mut PyObject, arg: *mut PyObject) -> *m
let args = args_array.as_ptr().offset(1); // For PY_VECTORCALL_ARGUMENTS_OFFSET
let tstate = PyThreadState_GET();
let nargsf = 1 | PY_VECTORCALL_ARGUMENTS_OFFSET;
_PyObject_VectorcallTstate(tstate, func, args, nargsf as size_t, std::ptr::null_mut())
_PyObject_VectorcallTstate(tstate, func, args, nargsf, std::ptr::null_mut())
}
extern "C" {
@ -206,7 +204,7 @@ pub unsafe fn PyObject_CallMethodNoArgs(
PyObject_VectorcallMethod(
name,
&self_,
1 | PY_VECTORCALL_ARGUMENTS_OFFSET as size_t,
1 | PY_VECTORCALL_ARGUMENTS_OFFSET,
std::ptr::null_mut(),
)
}
@ -223,7 +221,7 @@ pub unsafe fn PyObject_CallMethodOneArg(
PyObject_VectorcallMethod(
name,
args.as_ptr(),
2 | PY_VECTORCALL_ARGUMENTS_OFFSET as size_t,
2 | PY_VECTORCALL_ARGUMENTS_OFFSET,
std::ptr::null_mut(),
)
}

View File

@ -20,7 +20,11 @@ pub const _PY_MONITORING_EVENTS: usize = 17;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct _Py_LocalMonitors {
pub tools: [u8; _PY_MONITORING_UNGROUPED_EVENTS],
pub tools: [u8; if cfg!(Py_3_13) {
_PY_MONITORING_LOCAL_EVENTS
} else {
_PY_MONITORING_UNGROUPED_EVENTS
}],
}
#[cfg(Py_3_12)]
@ -102,6 +106,9 @@ pub struct PyCodeObject {
pub co_extra: *mut c_void,
}
#[cfg(Py_3_13)]
opaque_struct!(_PyExecutorArray);
#[cfg(all(not(any(PyPy, GraalPy)), Py_3_8, not(Py_3_11)))]
#[repr(C)]
#[derive(Copy, Clone)]
@ -176,6 +183,8 @@ pub struct PyCodeObject {
pub _co_code: *mut PyObject,
#[cfg(not(Py_3_12))]
pub _co_linearray: *mut c_char,
#[cfg(Py_3_13)]
pub co_executors: *mut _PyExecutorArray,
#[cfg(Py_3_12)]
pub _co_cached: *mut _PyCoCached,
#[cfg(Py_3_12)]

View File

@ -30,7 +30,7 @@ pub struct PyCompilerFlags {
// skipped non-limited _PyCompilerFlags_INIT
#[cfg(all(Py_3_12, not(any(PyPy, GraalPy))))]
#[cfg(all(Py_3_12, not(any(Py_3_13, PyPy, GraalPy))))]
#[repr(C)]
#[derive(Copy, Clone)]
pub struct _PyCompilerSrcLocation {
@ -42,7 +42,7 @@ pub struct _PyCompilerSrcLocation {
// skipped SRC_LOCATION_FROM_AST
#[cfg(not(any(PyPy, GraalPy)))]
#[cfg(not(any(PyPy, GraalPy, Py_3_13)))]
#[repr(C)]
#[derive(Copy, Clone)]
pub struct PyFutureFeatures {

View File

@ -57,7 +57,7 @@ pub struct _frozen {
pub size: c_int,
#[cfg(Py_3_11)]
pub is_package: c_int,
#[cfg(Py_3_11)]
#[cfg(all(Py_3_11, not(Py_3_13)))]
pub get_code: Option<unsafe extern "C" fn() -> *mut PyObject>,
}

View File

@ -141,6 +141,8 @@ pub struct PyConfig {
pub safe_path: c_int,
#[cfg(Py_3_12)]
pub int_max_str_digits: c_int,
#[cfg(Py_3_13)]
pub cpu_count: c_int,
pub pathconfig_warnings: c_int,
#[cfg(Py_3_10)]
pub program_name: *mut wchar_t,
@ -165,6 +167,8 @@ pub struct PyConfig {
pub run_command: *mut wchar_t,
pub run_module: *mut wchar_t,
pub run_filename: *mut wchar_t,
#[cfg(Py_3_13)]
pub sys_path_0: *mut wchar_t,
pub _install_importlib: c_int,
pub _init_main: c_int,
#[cfg(all(Py_3_9, not(Py_3_12)))]

View File

@ -0,0 +1,74 @@
use crate::longobject::*;
use crate::object::*;
#[cfg(Py_3_13)]
use crate::pyport::Py_ssize_t;
use libc::size_t;
#[cfg(Py_3_13)]
use std::os::raw::c_void;
use std::os::raw::{c_int, c_uchar};
#[cfg(Py_3_13)]
extern "C" {
pub fn PyLong_FromUnicodeObject(u: *mut PyObject, base: c_int) -> *mut PyObject;
}
#[cfg(Py_3_13)]
pub const Py_ASNATIVEBYTES_DEFAULTS: c_int = -1;
#[cfg(Py_3_13)]
pub const Py_ASNATIVEBYTES_BIG_ENDIAN: c_int = 0;
#[cfg(Py_3_13)]
pub const Py_ASNATIVEBYTES_LITTLE_ENDIAN: c_int = 1;
#[cfg(Py_3_13)]
pub const Py_ASNATIVEBYTES_NATIVE_ENDIAN: c_int = 3;
#[cfg(Py_3_13)]
pub const Py_ASNATIVEBYTES_UNSIGNED_BUFFER: c_int = 4;
#[cfg(Py_3_13)]
pub const Py_ASNATIVEBYTES_REJECT_NEGATIVE: c_int = 8;
extern "C" {
// skipped _PyLong_Sign
#[cfg(Py_3_13)]
pub fn PyLong_AsNativeBytes(
v: *mut PyObject,
buffer: *mut c_void,
n_bytes: Py_ssize_t,
flags: c_int,
) -> Py_ssize_t;
#[cfg(Py_3_13)]
pub fn PyLong_FromNativeBytes(
buffer: *const c_void,
n_bytes: size_t,
flags: c_int,
) -> *mut PyObject;
#[cfg(Py_3_13)]
pub fn PyLong_FromUnsignedNativeBytes(
buffer: *const c_void,
n_bytes: size_t,
flags: c_int,
) -> *mut PyObject;
// skipped PyUnstable_Long_IsCompact
// skipped PyUnstable_Long_CompactValue
#[cfg_attr(PyPy, link_name = "_PyPyLong_FromByteArray")]
pub fn _PyLong_FromByteArray(
bytes: *const c_uchar,
n: size_t,
little_endian: c_int,
is_signed: c_int,
) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")]
pub fn _PyLong_AsByteArray(
v: *mut PyLongObject,
bytes: *mut c_uchar,
n: size_t,
little_endian: c_int,
is_signed: c_int,
) -> c_int;
// skipped _PyLong_GCD
}

View File

@ -18,6 +18,7 @@ pub(crate) mod import;
pub(crate) mod initconfig;
// skipped interpreteridobject.h
pub(crate) mod listobject;
pub(crate) mod longobject;
#[cfg(all(Py_3_9, not(PyPy)))]
pub(crate) mod methodobject;
pub(crate) mod object;
@ -53,6 +54,7 @@ pub use self::import::*;
#[cfg(all(Py_3_8, not(PyPy)))]
pub use self::initconfig::*;
pub use self::listobject::*;
pub use self::longobject::*;
#[cfg(all(Py_3_9, not(PyPy)))]
pub use self::methodobject::*;
pub use self::object::*;

View File

@ -296,6 +296,8 @@ pub struct _specialization_cache {
pub getitem: *mut PyObject,
#[cfg(Py_3_12)]
pub getitem_version: u32,
#[cfg(Py_3_13)]
pub init: *mut PyObject,
}
#[repr(C)]

View File

@ -135,13 +135,9 @@ extern "C" {
}
#[inline]
#[cfg(not(GraalPy))]
#[cfg(not(any(PyPy, GraalPy)))]
pub unsafe fn Py_CompileString(string: *const c_char, p: *const c_char, s: c_int) -> *mut PyObject {
#[cfg(not(PyPy))]
return Py_CompileStringExFlags(string, p, s, std::ptr::null_mut(), -1);
#[cfg(PyPy)]
Py_CompileStringFlags(string, p, s, std::ptr::null_mut())
Py_CompileStringExFlags(string, p, s, std::ptr::null_mut(), -1)
}
#[inline]

View File

@ -449,19 +449,19 @@ pub const PyUnicode_1BYTE_KIND: c_uint = 1;
pub const PyUnicode_2BYTE_KIND: c_uint = 2;
pub const PyUnicode_4BYTE_KIND: c_uint = 4;
#[cfg(not(GraalPy))]
#[cfg(not(any(GraalPy, PyPy)))]
#[inline]
pub unsafe fn PyUnicode_1BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS1 {
PyUnicode_DATA(op) as *mut Py_UCS1
}
#[cfg(not(GraalPy))]
#[cfg(not(any(GraalPy, PyPy)))]
#[inline]
pub unsafe fn PyUnicode_2BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS2 {
PyUnicode_DATA(op) as *mut Py_UCS2
}
#[cfg(not(GraalPy))]
#[cfg(not(any(GraalPy, PyPy)))]
#[inline]
pub unsafe fn PyUnicode_4BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS4 {
PyUnicode_DATA(op) as *mut Py_UCS4
@ -487,7 +487,7 @@ pub unsafe fn _PyUnicode_COMPACT_DATA(op: *mut PyObject) -> *mut c_void {
}
}
#[cfg(not(GraalPy))]
#[cfg(not(any(GraalPy, PyPy)))]
#[inline]
pub unsafe fn _PyUnicode_NONCOMPACT_DATA(op: *mut PyObject) -> *mut c_void {
debug_assert!(!(*(op as *mut PyUnicodeObject)).data.any.is_null());
@ -495,7 +495,7 @@ pub unsafe fn _PyUnicode_NONCOMPACT_DATA(op: *mut PyObject) -> *mut c_void {
(*(op as *mut PyUnicodeObject)).data.any
}
#[cfg(not(GraalPy))]
#[cfg(not(any(GraalPy, PyPy)))]
#[inline]
pub unsafe fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void {
debug_assert!(crate::PyUnicode_Check(op) != 0);

View File

@ -13,7 +13,9 @@
use crate::{PyLong_AsLong, PyLong_Check, PyObject_GetAttrString, Py_DecRef};
use crate::{PyObject, PyObject_TypeCheck, PyTypeObject, Py_TYPE};
use std::cell::UnsafeCell;
use std::os::raw::{c_char, c_int};
#[cfg(not(GraalPy))]
use std::os::raw::c_char;
use std::os::raw::c_int;
use std::ptr;
#[cfg(not(PyPy))]
use {crate::PyCapsule_Import, std::ffi::CString};
@ -355,8 +357,8 @@ pub unsafe fn PyDateTime_DELTA_GET_MICROSECONDS(o: *mut PyObject) -> c_int {
// but copying them seems suboptimal
#[inline]
#[cfg(GraalPy)]
pub unsafe fn _get_attr(obj: *mut PyObject, field: &str) -> c_int {
let result = PyObject_GetAttrString(obj, field.as_ptr() as *const c_char);
pub unsafe fn _get_attr(obj: *mut PyObject, field: &std::ffi::CStr) -> c_int {
let result = PyObject_GetAttrString(obj, field.as_ptr());
Py_DecRef(result); // the original macros are borrowing
if PyLong_Check(result) == 1 {
PyLong_AsLong(result) as c_int
@ -368,55 +370,55 @@ pub unsafe fn _get_attr(obj: *mut PyObject, field: &str) -> c_int {
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_GET_YEAR(o: *mut PyObject) -> c_int {
_get_attr(o, "year\0")
_get_attr(o, c_str!("year"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_GET_MONTH(o: *mut PyObject) -> c_int {
_get_attr(o, "month\0")
_get_attr(o, c_str!("month"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_GET_DAY(o: *mut PyObject) -> c_int {
_get_attr(o, "day\0")
_get_attr(o, c_str!("day"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_DATE_GET_HOUR(o: *mut PyObject) -> c_int {
_get_attr(o, "hour\0")
_get_attr(o, c_str!("hour"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_DATE_GET_MINUTE(o: *mut PyObject) -> c_int {
_get_attr(o, "minute\0")
_get_attr(o, c_str!("minute"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_DATE_GET_SECOND(o: *mut PyObject) -> c_int {
_get_attr(o, "second\0")
_get_attr(o, c_str!("second"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_DATE_GET_MICROSECOND(o: *mut PyObject) -> c_int {
_get_attr(o, "microsecond\0")
_get_attr(o, c_str!("microsecond"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_DATE_GET_FOLD(o: *mut PyObject) -> c_int {
_get_attr(o, "fold\0")
_get_attr(o, c_str!("fold"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_DATE_GET_TZINFO(o: *mut PyObject) -> *mut PyObject {
let res = PyObject_GetAttrString(o, "tzinfo\0".as_ptr() as *const c_char);
let res = PyObject_GetAttrString(o, c_str!("tzinfo").as_ptr().cast());
Py_DecRef(res); // the original macros are borrowing
res
}
@ -424,37 +426,37 @@ pub unsafe fn PyDateTime_DATE_GET_TZINFO(o: *mut PyObject) -> *mut PyObject {
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_TIME_GET_HOUR(o: *mut PyObject) -> c_int {
_get_attr(o, "hour\0")
_get_attr(o, c_str!("hour"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_TIME_GET_MINUTE(o: *mut PyObject) -> c_int {
_get_attr(o, "minute\0")
_get_attr(o, c_str!("minute"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_TIME_GET_SECOND(o: *mut PyObject) -> c_int {
_get_attr(o, "second\0")
_get_attr(o, c_str!("second"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_TIME_GET_MICROSECOND(o: *mut PyObject) -> c_int {
_get_attr(o, "microsecond\0")
_get_attr(o, c_str!("microsecond"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_TIME_GET_FOLD(o: *mut PyObject) -> c_int {
_get_attr(o, "fold\0")
_get_attr(o, c_str!("fold"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_TIME_GET_TZINFO(o: *mut PyObject) -> *mut PyObject {
let res = PyObject_GetAttrString(o, "tzinfo\0".as_ptr() as *const c_char);
let res = PyObject_GetAttrString(o, c_str!("tzinfo").as_ptr().cast());
Py_DecRef(res); // the original macros are borrowing
res
}
@ -462,19 +464,19 @@ pub unsafe fn PyDateTime_TIME_GET_TZINFO(o: *mut PyObject) -> *mut PyObject {
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_DELTA_GET_DAYS(o: *mut PyObject) -> c_int {
_get_attr(o, "days\0")
_get_attr(o, c_str!("days"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_DELTA_GET_SECONDS(o: *mut PyObject) -> c_int {
_get_attr(o, "seconds\0")
_get_attr(o, c_str!("seconds"))
}
#[inline]
#[cfg(GraalPy)]
pub unsafe fn PyDateTime_DELTA_GET_MICROSECONDS(o: *mut PyObject) -> c_int {
_get_attr(o, "microseconds\0")
_get_attr(o, c_str!("microseconds"))
}
#[cfg(PyPy)]

View File

@ -51,7 +51,7 @@
//!
//! PyO3 supports the following software versions:
//! - Python 3.7 and up (CPython and PyPy)
//! - Rust 1.56 and up
//! - Rust 1.63 and up
//!
//! # Example: Building Python Native modules
//!
@ -88,10 +88,8 @@
//!
//! static mut MODULE_DEF: PyModuleDef = PyModuleDef {
//! m_base: PyModuleDef_HEAD_INIT,
//! m_name: "string_sum\0".as_ptr().cast::<c_char>(),
//! m_doc: "A Python module written in Rust.\0"
//! .as_ptr()
//! .cast::<c_char>(),
//! m_name: c_str!("string_sum").as_ptr(),
//! m_doc: c_str!("A Python module written in Rust.").as_ptr(),
//! m_size: 0,
//! m_methods: unsafe { METHODS.as_mut_ptr().cast() },
//! m_slots: std::ptr::null_mut(),
@ -102,14 +100,12 @@
//!
//! static mut METHODS: [PyMethodDef; 2] = [
//! PyMethodDef {
//! ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(),
//! ml_name: c_str!("sum_as_string").as_ptr(),
//! ml_meth: PyMethodDefPointer {
//! _PyCFunctionFast: sum_as_string,
//! },
//! ml_flags: METH_FASTCALL,
//! ml_doc: "returns the sum of two integers as a string\0"
//! .as_ptr()
//! .cast::<c_char>(),
//! ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
//! },
//! // A zeroed PyMethodDef to mark the end of the array.
//! PyMethodDef::zeroed()
@ -130,9 +126,7 @@
//! if nargs != 2 {
//! PyErr_SetString(
//! PyExc_TypeError,
//! "sum_as_string() expected 2 positional arguments\0"
//! .as_ptr()
//! .cast::<c_char>(),
//! c_str!("sum_as_string() expected 2 positional arguments").as_ptr(),
//! );
//! return std::ptr::null_mut();
//! }
@ -141,9 +135,7 @@
//! if PyLong_Check(arg1) == 0 {
//! PyErr_SetString(
//! PyExc_TypeError,
//! "sum_as_string() expected an int for positional argument 1\0"
//! .as_ptr()
//! .cast::<c_char>(),
//! c_str!("sum_as_string() expected an int for positional argument 1").as_ptr(),
//! );
//! return std::ptr::null_mut();
//! }
@ -157,9 +149,7 @@
//! if PyLong_Check(arg2) == 0 {
//! PyErr_SetString(
//! PyExc_TypeError,
//! "sum_as_string() expected an int for positional argument 2\0"
//! .as_ptr()
//! .cast::<c_char>(),
//! c_str!("sum_as_string() expected an int for positional argument 2").as_ptr(),
//! );
//! return std::ptr::null_mut();
//! }
@ -177,7 +167,7 @@
//! None => {
//! PyErr_SetString(
//! PyExc_OverflowError,
//! "arguments too large to add\0".as_ptr().cast::<c_char>(),
//! c_str!("arguments too large to add").as_ptr(),
//! );
//! std::ptr::null_mut()
//! }
@ -231,11 +221,10 @@
//! [`maturin`]: https://github.com/PyO3/maturin "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages"
//! [`pyo3-build-config`]: https://docs.rs/pyo3-build-config
//! [feature flags]: https://doc.rust-lang.org/cargo/reference/features.html "Features - The Cargo Book"
//! [manual_builds]: https://pyo3.rs/latest/building-and-distribution.html#manual-builds "Manual builds - Building and Distribution - PyO3 user guide"
#![doc = concat!("[manual_builds]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution.html#manual-builds \"Manual builds - Building and Distribution - PyO3 user guide\"")]
//! [setuptools-rust]: https://github.com/PyO3/setuptools-rust "Setuptools plugin for Rust extensions"
//! [PEP 384]: https://www.python.org/dev/peps/pep-0384 "PEP 384 -- Defining a Stable ABI"
//! [Features chapter of the guide]: https://pyo3.rs/latest/features.html#features-reference "Features Reference - PyO3 user guide"
#![doc = concat!("[Features chapter of the guide]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/features.html#features-reference \"Features Reference - PyO3 user guide\"")]
#![allow(
missing_docs,
non_camel_case_types,
@ -256,6 +245,51 @@ macro_rules! opaque_struct {
};
}
/// This is a helper macro to create a `&'static CStr`.
///
/// It can be used on all Rust versions supported by PyO3, unlike c"" literals which
/// were stabilised in Rust 1.77.
///
/// Due to the nature of PyO3 making heavy use of C FFI interop with Python, it is
/// common for PyO3 to use CStr.
///
/// Examples:
///
/// ```rust
/// use std::ffi::CStr;
///
/// const HELLO: &CStr = pyo3_ffi::c_str!("hello");
/// static WORLD: &CStr = pyo3_ffi::c_str!("world");
/// ```
#[macro_export]
macro_rules! c_str {
($s:expr) => {
$crate::_cstr_from_utf8_with_nul_checked(concat!($s, "\0"))
};
}
/// Private helper for `c_str!` macro.
#[doc(hidden)]
pub const fn _cstr_from_utf8_with_nul_checked(s: &str) -> &CStr {
// TODO: Replace this implementation with `CStr::from_bytes_with_nul` when MSRV above 1.72.
let bytes = s.as_bytes();
let len = bytes.len();
assert!(
!bytes.is_empty() && bytes[bytes.len() - 1] == b'\0',
"string is not nul-terminated"
);
let mut i = 0;
let non_null_len = len - 1;
while i < non_null_len {
assert!(bytes[i] != b'\0', "string contains null bytes");
i += 1;
}
unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }
}
use std::ffi::CStr;
pub use self::abstract_::*;
pub use self::bltinmodule::*;
pub use self::boolobject::*;

View File

@ -1,8 +1,6 @@
use crate::object::*;
use crate::pyport::Py_ssize_t;
use libc::size_t;
#[cfg(not(Py_LIMITED_API))]
use std::os::raw::c_uchar;
use std::os::raw::{c_char, c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void};
use std::ptr::addr_of_mut;
@ -90,34 +88,12 @@ extern "C" {
arg3: c_int,
) -> *mut PyObject;
}
// skipped non-limited PyLong_FromUnicodeObject
// skipped non-limited _PyLong_FromBytes
#[cfg(not(Py_LIMITED_API))]
extern "C" {
// skipped non-limited _PyLong_Sign
#[cfg_attr(PyPy, link_name = "_PyPyLong_NumBits")]
#[cfg(not(Py_3_13))]
pub fn _PyLong_NumBits(obj: *mut PyObject) -> size_t;
// skipped _PyLong_DivmodNear
#[cfg_attr(PyPy, link_name = "_PyPyLong_FromByteArray")]
pub fn _PyLong_FromByteArray(
bytes: *const c_uchar,
n: size_t,
little_endian: c_int,
is_signed: c_int,
) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")]
pub fn _PyLong_AsByteArray(
v: *mut PyLongObject,
bytes: *mut c_uchar,
n: size_t,
little_endian: c_int,
is_signed: c_int,
) -> c_int;
}
// skipped non-limited _PyLong_Format
@ -130,6 +106,5 @@ extern "C" {
pub fn PyOS_strtol(arg1: *const c_char, arg2: *mut *mut c_char, arg3: c_int) -> c_long;
}
// skipped non-limited _PyLong_GCD
// skipped non-limited _PyLong_Rshift
// skipped non-limited _PyLong_Lshift

View File

@ -186,9 +186,8 @@ impl std::fmt::Pointer for PyMethodDefPointer {
}
}
// TODO: This can be a const assert on Rust 1.57
const _: () =
[()][mem::size_of::<PyMethodDefPointer>() - mem::size_of::<Option<extern "C" fn()>>()];
assert!(mem::size_of::<PyMethodDefPointer>() == mem::size_of::<Option<extern "C" fn()>>());
#[cfg(not(Py_3_9))]
extern "C" {

View File

@ -261,6 +261,14 @@ extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyType_GetQualName")]
pub fn PyType_GetQualName(arg1: *mut PyTypeObject) -> *mut PyObject;
#[cfg(Py_3_13)]
#[cfg_attr(PyPy, link_name = "PyPyType_GetFullyQualifiedName")]
pub fn PyType_GetFullyQualifiedName(arg1: *mut PyTypeObject) -> *mut PyObject;
#[cfg(Py_3_13)]
#[cfg_attr(PyPy, link_name = "PyPyType_GetModuleName")]
pub fn PyType_GetModuleName(arg1: *mut PyTypeObject) -> *mut PyObject;
#[cfg(Py_3_12)]
#[cfg_attr(PyPy, link_name = "PyPyType_FromMetaclass")]
pub fn PyType_FromMetaclass(

View File

@ -101,7 +101,7 @@ pub unsafe fn PyUnicodeDecodeError_Create(
) -> *mut PyObject {
crate::_PyObject_CallFunction_SizeT(
PyExc_UnicodeDecodeError,
b"sy#nns\0".as_ptr().cast::<c_char>(),
c_str!("sy#nns").as_ptr(),
encoding,
object,
length,

View File

@ -11,8 +11,8 @@ pub type Py_ssize_t = ::libc::ssize_t;
pub type Py_hash_t = Py_ssize_t;
pub type Py_uhash_t = ::libc::size_t;
pub const PY_SSIZE_T_MIN: Py_ssize_t = std::isize::MIN as Py_ssize_t;
pub const PY_SSIZE_T_MAX: Py_ssize_t = std::isize::MAX as Py_ssize_t;
pub const PY_SSIZE_T_MIN: Py_ssize_t = isize::MIN as Py_ssize_t;
pub const PY_SSIZE_T_MAX: Py_ssize_t = isize::MAX as Py_ssize_t;
#[cfg(target_endian = "big")]
pub const PY_BIG_ENDIAN: usize = 1;

View File

@ -1,7 +1,7 @@
use crate::object::*;
#[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))]
use libc::FILE;
#[cfg(all(not(PyPy), any(Py_LIMITED_API, not(Py_3_10), GraalPy)))]
#[cfg(any(Py_LIMITED_API, not(Py_3_10), PyPy, GraalPy))]
use std::os::raw::c_char;
use std::os::raw::c_int;
@ -20,6 +20,28 @@ extern "C" {
pub fn PyErr_DisplayException(exc: *mut PyObject);
}
#[inline]
#[cfg(PyPy)]
pub unsafe fn Py_CompileString(string: *const c_char, p: *const c_char, s: c_int) -> *mut PyObject {
// PyPy's implementation of Py_CompileString always forwards to Py_CompileStringFlags; this
// is only available in the non-limited API and has a real definition for all versions in
// the cpython/ subdirectory.
#[cfg(Py_LIMITED_API)]
extern "C" {
#[link_name = "PyPy_CompileStringFlags"]
pub fn Py_CompileStringFlags(
string: *const c_char,
p: *const c_char,
s: c_int,
f: *mut std::os::raw::c_void, // Actually *mut Py_CompilerFlags in the real definition
) -> *mut PyObject;
}
#[cfg(not(Py_LIMITED_API))]
use crate::Py_CompileStringFlags;
Py_CompileStringFlags(string, p, s, std::ptr::null_mut())
}
// skipped PyOS_InputHook
pub const PYOS_STACK_MARGIN: c_int = 2048;

View File

@ -328,6 +328,15 @@ extern "C" {
pub fn PyUnicode_Compare(left: *mut PyObject, right: *mut PyObject) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyUnicode_CompareWithASCIIString")]
pub fn PyUnicode_CompareWithASCIIString(left: *mut PyObject, right: *const c_char) -> c_int;
#[cfg(Py_3_13)]
pub fn PyUnicode_EqualToUTF8(unicode: *mut PyObject, string: *const c_char) -> c_int;
#[cfg(Py_3_13)]
pub fn PyUnicode_EqualToUTF8AndSize(
unicode: *mut PyObject,
string: *const c_char,
size: Py_ssize_t,
) -> c_int;
pub fn PyUnicode_RichCompare(
left: *mut PyObject,
right: *mut PyObject,

View File

@ -1,6 +1,6 @@
[package]
name = "pyo3-macros-backend"
version = "0.21.1"
version = "0.23.0-dev"
description = "Code generation for PyO3 package"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"]
@ -14,18 +14,22 @@ edition = "2021"
# not to depend on proc-macro itself.
# See https://github.com/PyO3/pyo3/pull/810 for more.
[dependencies]
heck = "0.4"
proc-macro2 = { version = "1", default-features = false }
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.21.1", features = ["resolve-config"] }
heck = "0.5"
proc-macro2 = { version = "1.0.60", default-features = false }
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.0-dev", features = ["resolve-config"] }
quote = { version = "1", default-features = false }
[dependencies.syn]
version = "2"
version = "2.0.59" # for `LitCStr`
default-features = false
features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"]
[build-dependencies]
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.0-dev" }
[lints]
workspace = true
[features]
experimental-async = []
gil-refs = []

View File

@ -0,0 +1,4 @@
fn main() {
pyo3_build_config::print_expected_cfgs();
pyo3_build_config::print_feature_cfgs();
}

View File

@ -1,6 +1,7 @@
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{
ext::IdentExt,
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
@ -12,18 +13,23 @@ pub mod kw {
syn::custom_keyword!(annotation);
syn::custom_keyword!(attribute);
syn::custom_keyword!(cancel_handle);
syn::custom_keyword!(constructor);
syn::custom_keyword!(dict);
syn::custom_keyword!(eq);
syn::custom_keyword!(eq_int);
syn::custom_keyword!(extends);
syn::custom_keyword!(freelist);
syn::custom_keyword!(from_py_with);
syn::custom_keyword!(frozen);
syn::custom_keyword!(get);
syn::custom_keyword!(get_all);
syn::custom_keyword!(hash);
syn::custom_keyword!(item);
syn::custom_keyword!(from_item_all);
syn::custom_keyword!(mapping);
syn::custom_keyword!(module);
syn::custom_keyword!(name);
syn::custom_keyword!(ord);
syn::custom_keyword!(pass_module);
syn::custom_keyword!(rename_all);
syn::custom_keyword!(sequence);
@ -31,6 +37,7 @@ pub mod kw {
syn::custom_keyword!(set_all);
syn::custom_keyword!(signature);
syn::custom_keyword!(subclass);
syn::custom_keyword!(submodule);
syn::custom_keyword!(text_signature);
syn::custom_keyword!(transparent);
syn::custom_keyword!(unsendable);
@ -68,7 +75,7 @@ pub struct NameLitStr(pub Ident);
impl Parse for NameLitStr {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let string_literal: LitStr = input.parse()?;
if let Ok(ident) = string_literal.parse() {
if let Ok(ident) = string_literal.parse_with(Ident::parse_any) {
Ok(NameLitStr(ident))
} else {
bail_spanned!(string_literal.span() => "expected a single identifier in double quotes")
@ -172,6 +179,7 @@ pub type ModuleAttribute = KeywordAttribute<kw::module, LitStr>;
pub type NameAttribute = KeywordAttribute<kw::name, NameLitStr>;
pub type RenameAllAttribute = KeywordAttribute<kw::rename_all, RenamingRuleLitStr>;
pub type TextSignatureAttribute = KeywordAttribute<kw::text_signature, TextSignatureAttributeValue>;
pub type SubmoduleAttribute = kw::submodule;
impl<K: Parse + std::fmt::Debug, V: Parse> Parse for KeywordAttribute<K, V> {
fn parse(input: ParseStream<'_>) -> Result<Self> {

View File

@ -1,47 +1,54 @@
use crate::utils::Ctx;
use proc_macro2::{Span, TokenStream};
use quote::{quote_spanned, ToTokens};
use crate::method::{FnArg, FnSpec};
use proc_macro2::TokenStream;
use quote::quote_spanned;
pub enum Deprecation {
PyMethodsNewDeprecatedForm,
}
impl Deprecation {
fn ident(&self, span: Span) -> syn::Ident {
let string = match self {
Deprecation::PyMethodsNewDeprecatedForm => "PYMETHODS_NEW_DEPRECATED_FORM",
};
syn::Ident::new(string, span)
pub(crate) fn deprecate_trailing_option_default(spec: &FnSpec<'_>) -> TokenStream {
if spec.signature.attribute.is_none()
&& spec.tp.signature_attribute_allowed()
&& spec.signature.arguments.iter().any(|arg| {
if let FnArg::Regular(arg) = arg {
arg.option_wrapped_type.is_some()
} else {
false
}
}
pub struct Deprecations<'ctx>(Vec<(Deprecation, Span)>, &'ctx Ctx);
impl<'ctx> Deprecations<'ctx> {
pub fn new(ctx: &'ctx Ctx) -> Self {
Deprecations(Vec::new(), ctx)
}
pub fn push(&mut self, deprecation: Deprecation, span: Span) {
self.0.push((deprecation, span))
}
}
impl<'ctx> ToTokens for Deprecations<'ctx> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self(deprecations, Ctx { pyo3_path }) = self;
for (deprecation, span) in deprecations {
let pyo3_path = pyo3_path.to_tokens_spanned(*span);
let ident = deprecation.ident(*span);
quote_spanned!(
*span =>
#[allow(clippy::let_unit_value)]
})
{
let _ = #pyo3_path::impl_::deprecations::#ident;
use std::fmt::Write;
let mut deprecation_msg = String::from(
"this function has implicit defaults for the trailing `Option<T>` arguments \n\
= note: these implicit defaults are being phased out \n\
= help: add `#[pyo3(signature = (",
);
spec.signature.arguments.iter().for_each(|arg| {
match arg {
FnArg::Regular(arg) => {
if arg.option_wrapped_type.is_some() {
write!(deprecation_msg, "{}=None, ", arg.name)
} else {
write!(deprecation_msg, "{}, ", arg.name)
}
)
.to_tokens(tokens)
}
FnArg::VarArgs(arg) => write!(deprecation_msg, "{}, ", arg.name),
FnArg::KwArgs(arg) => write!(deprecation_msg, "{}, ", arg.name),
FnArg::Py(_) | FnArg::CancelHandle(_) => Ok(()),
}
.expect("writing to `String` should not fail");
});
//remove trailing space and comma
deprecation_msg.pop();
deprecation_msg.pop();
deprecation_msg.push_str(
"))]` to this function to silence this warning and keep the current behavior",
);
quote_spanned! { spec.name.span() =>
#[deprecated(note = #deprecation_msg)]
#[allow(dead_code)]
const SIGNATURE: () = ();
const _: () = SIGNATURE;
}
} else {
TokenStream::new()
}
}

View File

@ -1,7 +1,7 @@
use crate::attributes::{self, get_pyo3_options, CrateAttribute, FromPyWithAttribute};
use crate::utils::Ctx;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, quote_spanned};
use quote::{format_ident, quote};
use syn::{
parenthesized,
parse::{Parse, ParseStream},
@ -44,16 +44,14 @@ impl<'a> Enum<'a> {
}
/// Build derivation body for enums.
fn build(&self, ctx: &Ctx) -> (TokenStream, TokenStream) {
let Ctx { pyo3_path } = ctx;
fn build(&self, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path, .. } = ctx;
let mut var_extracts = Vec::new();
let mut variant_names = Vec::new();
let mut error_names = Vec::new();
let mut deprecations = TokenStream::new();
for var in &self.variants {
let (struct_derive, dep) = var.build(ctx);
deprecations.extend(dep);
let struct_derive = var.build(ctx);
let ext = quote!({
let maybe_ret = || -> #pyo3_path::PyResult<Self> {
#struct_derive
@ -70,7 +68,6 @@ impl<'a> Enum<'a> {
error_names.push(&var.err_name);
}
let ty_name = self.enum_ident.to_string();
(
quote!(
let errors = [
#(#var_extracts),*
@ -84,8 +81,6 @@ impl<'a> Enum<'a> {
&errors
)
)
),
deprecations,
)
}
}
@ -244,7 +239,7 @@ impl<'a> Container<'a> {
}
/// Build derivation body for a struct.
fn build(&self, ctx: &Ctx) -> (TokenStream, TokenStream) {
fn build(&self, ctx: &Ctx) -> TokenStream {
match &self.ty {
ContainerType::StructNewtype(ident, from_py_with) => {
self.build_newtype_struct(Some(ident), from_py_with, ctx)
@ -262,74 +257,43 @@ impl<'a> Container<'a> {
field_ident: Option<&Ident>,
from_py_with: &Option<FromPyWithAttribute>,
ctx: &Ctx,
) -> (TokenStream, TokenStream) {
let Ctx { pyo3_path } = ctx;
) -> TokenStream {
let Ctx { pyo3_path, .. } = ctx;
let self_ty = &self.path;
let struct_name = self.name();
if let Some(ident) = field_ident {
let field_name = ident.to_string();
match from_py_with {
None => (
quote! {
None => quote! {
Ok(#self_ty {
#ident: #pyo3_path::impl_::frompyobject::extract_struct_field(obj, #struct_name, #field_name)?
})
},
TokenStream::new(),
),
Some(FromPyWithAttribute {
value: expr_path, ..
}) => (
quote! {
}) => quote! {
Ok(#self_ty {
#ident: #pyo3_path::impl_::frompyobject::extract_struct_field_with(#expr_path as fn(_) -> _, obj, #struct_name, #field_name)?
})
},
quote_spanned! { expr_path.span() =>
const _: () = {
fn check_from_py_with() {
let e = #pyo3_path::impl_::deprecations::GilRefs::new();
#pyo3_path::impl_::deprecations::inspect_fn(#expr_path, &e);
e.from_py_with_arg();
}
};
},
),
}
} else {
match from_py_with {
None => (
quote!(
None => quote! {
#pyo3_path::impl_::frompyobject::extract_tuple_struct_field(obj, #struct_name, 0).map(#self_ty)
),
TokenStream::new(),
),
},
Some(FromPyWithAttribute {
value: expr_path, ..
}) => (
quote! (
}) => quote! {
#pyo3_path::impl_::frompyobject::extract_tuple_struct_field_with(#expr_path as fn(_) -> _, obj, #struct_name, 0).map(#self_ty)
),
quote_spanned! { expr_path.span() =>
const _: () = {
fn check_from_py_with() {
let e = #pyo3_path::impl_::deprecations::GilRefs::new();
#pyo3_path::impl_::deprecations::inspect_fn(#expr_path, &e);
e.from_py_with_arg();
}
};
},
),
}
}
}
fn build_tuple_struct(
&self,
struct_fields: &[TupleStructField],
ctx: &Ctx,
) -> (TokenStream, TokenStream) {
let Ctx { pyo3_path } = ctx;
fn build_tuple_struct(&self, struct_fields: &[TupleStructField], ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path, .. } = ctx;
let self_ty = &self.path;
let struct_name = &self.name();
let field_idents: Vec<_> = (0..struct_fields.len())
@ -348,41 +312,16 @@ impl<'a> Container<'a> {
}
});
let deprecations = struct_fields
.iter()
.filter_map(|field| {
let FromPyWithAttribute {
value: expr_path, ..
} = field.from_py_with.as_ref()?;
Some(quote_spanned! { expr_path.span() =>
const _: () = {
fn check_from_py_with() {
let e = #pyo3_path::impl_::deprecations::GilRefs::new();
#pyo3_path::impl_::deprecations::inspect_fn(#expr_path, &e);
e.from_py_with_arg();
}
};
})
})
.collect::<TokenStream>();
(
quote!(
match #pyo3_path::types::PyAnyMethods::extract(obj) {
::std::result::Result::Ok((#(#field_idents),*)) => ::std::result::Result::Ok(#self_ty(#(#fields),*)),
::std::result::Result::Err(err) => ::std::result::Result::Err(err),
}
),
deprecations,
)
}
fn build_struct(
&self,
struct_fields: &[NamedStructField<'_>],
ctx: &Ctx,
) -> (TokenStream, TokenStream) {
let Ctx { pyo3_path } = ctx;
fn build_struct(&self, struct_fields: &[NamedStructField<'_>], ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path, .. } = ctx;
let self_ty = &self.path;
let struct_name = &self.name();
let mut fields: Punctuated<TokenStream, syn::Token![,]> = Punctuated::new();
@ -420,28 +359,7 @@ impl<'a> Container<'a> {
fields.push(quote!(#ident: #extractor));
}
let deprecations = struct_fields
.iter()
.filter_map(|field| {
let FromPyWithAttribute {
value: expr_path, ..
} = field.from_py_with.as_ref()?;
Some(quote_spanned! { expr_path.span() =>
const _: () = {
fn check_from_py_with() {
let e = #pyo3_path::impl_::deprecations::GilRefs::new();
#pyo3_path::impl_::deprecations::inspect_fn(#expr_path, &e);
e.from_py_with_arg();
}
};
})
})
.collect::<TokenStream>();
(
quote!(::std::result::Result::Ok(#self_ty{#fields})),
deprecations,
)
quote!(::std::result::Result::Ok(#self_ty{#fields}))
}
}
@ -670,10 +588,10 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
.push(parse_quote!(#gen_ident: FromPyObject<#lt_param>))
}
let options = ContainerOptions::from_attrs(&tokens.attrs)?;
let ctx = &Ctx::new(&options.krate);
let Ctx { pyo3_path } = &ctx;
let ctx = &Ctx::new(&options.krate, None);
let Ctx { pyo3_path, .. } = &ctx;
let (derives, from_py_with_deprecations) = match &tokens.data {
let derives = match &tokens.data {
syn::Data::Enum(en) => {
if options.transparent || options.annotation.is_some() {
bail_spanned!(tokens.span() => "`transparent` or `annotation` is not supported \
@ -703,7 +621,5 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
#derives
}
}
#from_py_with_deprecations
))
}

View File

@ -1,12 +1,9 @@
use std::borrow::Cow;
use std::ffi::CString;
use crate::utils::Ctx;
use crate::{
attributes::{self, get_pyo3_options, take_attributes, NameAttribute},
deprecations::Deprecations,
};
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use crate::attributes::{self, get_pyo3_options, take_attributes, NameAttribute};
use crate::utils::{Ctx, LitCStr};
use proc_macro2::{Ident, Span};
use syn::{
ext::IdentExt,
parse::{Parse, ParseStream},
@ -14,12 +11,12 @@ use syn::{
Result,
};
pub struct ConstSpec<'ctx> {
pub struct ConstSpec {
pub rust_ident: syn::Ident,
pub attributes: ConstAttributes<'ctx>,
pub attributes: ConstAttributes,
}
impl ConstSpec<'_> {
impl ConstSpec {
pub fn python_name(&self) -> Cow<'_, Ident> {
if let Some(name) = &self.attributes.name {
Cow::Borrowed(&name.value.0)
@ -29,16 +26,15 @@ impl ConstSpec<'_> {
}
/// Null-terminated Python name
pub fn null_terminated_python_name(&self) -> TokenStream {
let name = format!("{}\0", self.python_name());
quote!({#name})
pub fn null_terminated_python_name(&self, ctx: &Ctx) -> LitCStr {
let name = self.python_name().to_string();
LitCStr::new(CString::new(name).unwrap(), Span::call_site(), ctx)
}
}
pub struct ConstAttributes<'ctx> {
pub struct ConstAttributes {
pub is_class_attr: bool,
pub name: Option<NameAttribute>,
pub deprecations: Deprecations<'ctx>,
}
pub enum PyO3ConstAttribute {
@ -56,12 +52,11 @@ impl Parse for PyO3ConstAttribute {
}
}
impl<'ctx> ConstAttributes<'ctx> {
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>, ctx: &'ctx Ctx) -> syn::Result<Self> {
impl ConstAttributes {
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
let mut attributes = ConstAttributes {
is_class_attr: false,
name: None,
deprecations: Deprecations::new(ctx),
};
take_attributes(attrs, |attr| {

View File

@ -19,6 +19,7 @@ mod pyclass;
mod pyfunction;
mod pyimpl;
mod pymethod;
mod pyversions;
mod quotes;
pub use frompyobject::build_derive_from_pyobject;

View File

@ -1,13 +1,15 @@
use std::borrow::Cow;
use std::ffi::CString;
use std::fmt::Display;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens};
use syn::{ext::IdentExt, spanned::Spanned, Ident, Result};
use crate::utils::Ctx;
use crate::deprecations::deprecate_trailing_option_default;
use crate::utils::{Ctx, LitCStr};
use crate::{
attributes::{TextSignatureAttribute, TextSignatureAttributeValue},
deprecations::{Deprecation, Deprecations},
attributes::{FromPyWithAttribute, TextSignatureAttribute, TextSignatureAttributeValue},
params::{impl_arg_params, Holders},
pyfunction::{
FunctionSignature, PyFunctionArgPyO3Attributes, PyFunctionOptions, SignatureAttribute,
@ -17,19 +19,115 @@ use crate::{
};
#[derive(Clone, Debug)]
pub struct FnArg<'a> {
pub struct RegularArg<'a> {
pub name: Cow<'a, syn::Ident>,
pub ty: &'a syn::Type,
pub from_py_with: Option<FromPyWithAttribute>,
pub default_value: Option<syn::Expr>,
pub option_wrapped_type: Option<&'a syn::Type>,
}
/// Pythons *args argument
#[derive(Clone, Debug)]
pub struct VarargsArg<'a> {
pub name: Cow<'a, syn::Ident>,
pub ty: &'a syn::Type,
}
/// Pythons **kwarg argument
#[derive(Clone, Debug)]
pub struct KwargsArg<'a> {
pub name: Cow<'a, syn::Ident>,
pub ty: &'a syn::Type,
}
#[derive(Clone, Debug)]
pub struct CancelHandleArg<'a> {
pub name: &'a syn::Ident,
pub ty: &'a syn::Type,
pub optional: Option<&'a syn::Type>,
pub default: Option<syn::Expr>,
pub py: bool,
pub attrs: PyFunctionArgPyO3Attributes,
pub is_varargs: bool,
pub is_kwargs: bool,
pub is_cancel_handle: bool,
}
#[derive(Clone, Debug)]
pub struct PyArg<'a> {
pub name: &'a syn::Ident,
pub ty: &'a syn::Type,
}
#[derive(Clone, Debug)]
pub enum FnArg<'a> {
Regular(RegularArg<'a>),
VarArgs(VarargsArg<'a>),
KwArgs(KwargsArg<'a>),
Py(PyArg<'a>),
CancelHandle(CancelHandleArg<'a>),
}
impl<'a> FnArg<'a> {
pub fn name(&self) -> &syn::Ident {
match self {
FnArg::Regular(RegularArg { name, .. }) => name,
FnArg::VarArgs(VarargsArg { name, .. }) => name,
FnArg::KwArgs(KwargsArg { name, .. }) => name,
FnArg::Py(PyArg { name, .. }) => name,
FnArg::CancelHandle(CancelHandleArg { name, .. }) => name,
}
}
pub fn ty(&self) -> &'a syn::Type {
match self {
FnArg::Regular(RegularArg { ty, .. }) => ty,
FnArg::VarArgs(VarargsArg { ty, .. }) => ty,
FnArg::KwArgs(KwargsArg { ty, .. }) => ty,
FnArg::Py(PyArg { ty, .. }) => ty,
FnArg::CancelHandle(CancelHandleArg { ty, .. }) => ty,
}
}
#[allow(clippy::wrong_self_convention)]
pub fn from_py_with(&self) -> Option<&FromPyWithAttribute> {
if let FnArg::Regular(RegularArg { from_py_with, .. }) = self {
from_py_with.as_ref()
} else {
None
}
}
pub fn to_varargs_mut(&mut self) -> Result<&mut Self> {
if let Self::Regular(RegularArg {
name,
ty,
option_wrapped_type: None,
..
}) = self
{
*self = Self::VarArgs(VarargsArg {
name: name.clone(),
ty,
});
Ok(self)
} else {
bail_spanned!(self.name().span() => "args cannot be optional")
}
}
pub fn to_kwargs_mut(&mut self) -> Result<&mut Self> {
if let Self::Regular(RegularArg {
name,
ty,
option_wrapped_type: Some(..),
..
}) = self
{
*self = Self::KwArgs(KwargsArg {
name: name.clone(),
ty,
});
Ok(self)
} else {
bail_spanned!(self.name().span() => "kwargs must be Option<_>")
}
}
/// Transforms a rust fn arg parsed with syn into a method::FnArg
pub fn parse(arg: &'a mut syn::FnArg) -> Result<Self> {
match arg {
@ -41,31 +139,42 @@ impl<'a> FnArg<'a> {
bail_spanned!(cap.ty.span() => IMPL_TRAIT_ERR);
}
let arg_attrs = PyFunctionArgPyO3Attributes::from_attrs(&mut cap.attrs)?;
let PyFunctionArgPyO3Attributes {
from_py_with,
cancel_handle,
} = PyFunctionArgPyO3Attributes::from_attrs(&mut cap.attrs)?;
let ident = match &*cap.pat {
syn::Pat::Ident(syn::PatIdent { ident, .. }) => ident,
other => return Err(handle_argument_error(other)),
};
let is_cancel_handle = arg_attrs.cancel_handle.is_some();
Ok(FnArg {
if utils::is_python(&cap.ty) {
return Ok(Self::Py(PyArg {
name: ident,
ty: &cap.ty,
optional: utils::option_type_argument(&cap.ty),
default: None,
py: utils::is_python(&cap.ty),
attrs: arg_attrs,
is_varargs: false,
is_kwargs: false,
is_cancel_handle,
})
}
}
}));
}
pub fn is_regular(&self) -> bool {
!self.py && !self.is_cancel_handle && !self.is_kwargs && !self.is_varargs
if cancel_handle.is_some() {
// `PyFunctionArgPyO3Attributes::from_attrs` validates that
// only compatible attributes are specified, either
// `cancel_handle` or `from_py_with`, dublicates and any
// combination of the two are already rejected.
return Ok(Self::CancelHandle(CancelHandleArg {
name: ident,
ty: &cap.ty,
}));
}
Ok(Self::Regular(RegularArg {
name: Cow::Borrowed(ident),
ty: &cap.ty,
from_py_with,
default_value: None,
option_wrapped_type: utils::option_type_argument(&cap.ty),
}))
}
}
}
}
@ -82,16 +191,26 @@ fn handle_argument_error(pat: &syn::Pat) -> syn::Error {
syn::Error::new(span, msg)
}
/// Represents what kind of a function a pyfunction or pymethod is
#[derive(Clone, Debug)]
pub enum FnType {
/// Represents a pymethod annotated with `#[getter]`
Getter(SelfType),
/// Represents a pymethod annotated with `#[setter]`
Setter(SelfType),
/// Represents a regular pymethod
Fn(SelfType),
/// Represents a pymethod annotated with `#[new]`, i.e. the `__new__` dunder.
FnNew,
/// Represents a pymethod annotated with both `#[new]` and `#[classmethod]` (in either order)
FnNewClass(Span),
/// Represents a pymethod annotated with `#[classmethod]`, like a `@classmethod`
FnClass(Span),
/// Represents a pyfunction or a pymethod annotated with `#[staticmethod]`, like a `@staticmethod`
FnStatic,
/// Represents a pyfunction annotated with `#[pyo3(pass_module)]
FnModule(Span),
/// Represents a pymethod or associated constant annotated with `#[classattr]`
ClassAttribute,
}
@ -108,14 +227,28 @@ impl FnType {
}
}
pub fn signature_attribute_allowed(&self) -> bool {
match self {
FnType::Fn(_)
| FnType::FnNew
| FnType::FnStatic
| FnType::FnClass(_)
| FnType::FnNewClass(_)
| FnType::FnModule(_) => true,
// Setter, Getter and ClassAttribute all have fixed signatures (either take 0 or 1
// arguments) so cannot have a `signature = (...)` attribute.
FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => false,
}
}
pub fn self_arg(
&self,
cls: Option<&syn::Type>,
error_mode: ExtractErrorMode,
holders: &mut Holders,
ctx: &Ctx,
) -> TokenStream {
let Ctx { pyo3_path } = ctx;
) -> Option<TokenStream> {
let Ctx { pyo3_path, .. } = ctx;
match self {
FnType::Getter(st) | FnType::Setter(st) | FnType::Fn(st) => {
let mut receiver = st.receiver(
@ -125,35 +258,35 @@ impl FnType {
ctx,
);
syn::Token![,](Span::call_site()).to_tokens(&mut receiver);
receiver
}
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => {
quote!()
Some(receiver)
}
FnType::FnClass(span) | FnType::FnNewClass(span) => {
let py = syn::Ident::new("py", Span::call_site());
let slf: Ident = syn::Ident::new("_slf_ref", Span::call_site());
let pyo3_path = pyo3_path.to_tokens_spanned(*span);
quote_spanned! { *span =>
let ret = quote_spanned! { *span =>
#[allow(clippy::useless_conversion)]
::std::convert::Into::into(
#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &*(#slf as *const _ as *const *mut _))
.downcast_unchecked::<#pyo3_path::types::PyType>()
),
}
};
Some(ret)
}
FnType::FnModule(span) => {
let py = syn::Ident::new("py", Span::call_site());
let slf: Ident = syn::Ident::new("_slf_ref", Span::call_site());
let pyo3_path = pyo3_path.to_tokens_spanned(*span);
quote_spanned! { *span =>
let ret = quote_spanned! { *span =>
#[allow(clippy::useless_conversion)]
::std::convert::Into::into(
#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &*(#slf as *const _ as *const *mut _))
.downcast_unchecked::<#pyo3_path::types::PyModule>()
),
};
Some(ret)
}
}
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => None,
}
}
}
@ -172,7 +305,7 @@ pub enum ExtractErrorMode {
impl ExtractErrorMode {
pub fn handle_error(self, extract: TokenStream, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
match self {
ExtractErrorMode::Raise => quote! { #extract? },
ExtractErrorMode::NotImplemented => quote! {
@ -197,7 +330,7 @@ impl SelfType {
// main macro callsite.
let py = syn::Ident::new("py", Span::call_site());
let slf = syn::Ident::new("_slf", Span::call_site());
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
match self {
SelfType::Receiver { span, mutable } => {
let method = if *mutable {
@ -277,7 +410,6 @@ pub struct FnSpec<'a> {
pub text_signature: Option<TextSignatureAttribute>,
pub asyncness: Option<syn::Token![async]>,
pub unsafety: Option<syn::Token![unsafe]>,
pub deprecations: Deprecations<'a>,
}
pub fn parse_method_receiver(arg: &syn::FnArg) -> Result<SelfType> {
@ -309,7 +441,6 @@ impl<'a> FnSpec<'a> {
sig: &'a mut syn::Signature,
meth_attrs: &mut Vec<syn::Attribute>,
options: PyFunctionOptions,
ctx: &'a Ctx,
) -> Result<FnSpec<'a>> {
let PyFunctionOptions {
text_signature,
@ -319,9 +450,8 @@ impl<'a> FnSpec<'a> {
} = options;
let mut python_name = name.map(|name| name.value.0);
let mut deprecations = Deprecations::new(ctx);
let fn_type = Self::parse_fn_type(sig, meth_attrs, &mut python_name, &mut deprecations)?;
let fn_type = Self::parse_fn_type(sig, meth_attrs, &mut python_name)?;
ensure_signatures_on_valid_method(&fn_type, signature.as_ref(), text_signature.as_ref())?;
let name = &sig.ident;
@ -359,21 +489,21 @@ impl<'a> FnSpec<'a> {
text_signature,
asyncness: sig.asyncness,
unsafety: sig.unsafety,
deprecations,
})
}
pub fn null_terminated_python_name(&self) -> syn::LitStr {
syn::LitStr::new(&format!("{}\0", self.python_name), self.python_name.span())
pub fn null_terminated_python_name(&self, ctx: &Ctx) -> LitCStr {
let name = self.python_name.to_string();
let name = CString::new(name).unwrap();
LitCStr::new(name, self.python_name.span(), ctx)
}
fn parse_fn_type(
sig: &syn::Signature,
meth_attrs: &mut Vec<syn::Attribute>,
python_name: &mut Option<syn::Ident>,
deprecations: &mut Deprecations<'_>,
) -> Result<FnType> {
let mut method_attributes = parse_method_attributes(meth_attrs, deprecations)?;
let mut method_attributes = parse_method_attributes(meth_attrs)?;
let name = &sig.ident;
let parse_receiver = |msg: &'static str| {
@ -487,17 +617,22 @@ impl<'a> FnSpec<'a> {
cls: Option<&syn::Type>,
ctx: &Ctx,
) -> Result<TokenStream> {
let Ctx { pyo3_path } = ctx;
let Ctx {
pyo3_path,
output_span,
} = ctx;
let mut cancel_handle_iter = self
.signature
.arguments
.iter()
.filter(|arg| arg.is_cancel_handle);
.filter(|arg| matches!(arg, FnArg::CancelHandle(..)));
let cancel_handle = cancel_handle_iter.next();
if let Some(arg) = cancel_handle {
ensure_spanned!(self.asyncness.is_some(), arg.name.span() => "`cancel_handle` attribute can only be used with `async fn`");
if let Some(arg2) = cancel_handle_iter.next() {
bail_spanned!(arg2.name.span() => "`cancel_handle` may only be specified once");
if let Some(FnArg::CancelHandle(CancelHandleArg { name, .. })) = cancel_handle {
ensure_spanned!(self.asyncness.is_some(), name.span() => "`cancel_handle` attribute can only be used with `async fn`");
if let Some(FnArg::CancelHandle(CancelHandleArg { name, .. })) =
cancel_handle_iter.next()
{
bail_spanned!(name.span() => "`cancel_handle` may only be specified once");
}
}
@ -522,48 +657,35 @@ impl<'a> FnSpec<'a> {
Some(cls) => quote!(Some(<#cls as #pyo3_path::PyTypeInfo>::NAME)),
None => quote!(None),
};
let evaluate_args = || -> (Vec<Ident>, TokenStream) {
let mut arg_names = Vec::with_capacity(args.len());
let mut evaluate_arg = quote! {};
for arg in &args {
let arg_name = format_ident!("arg_{}", arg_names.len());
arg_names.push(arg_name.clone());
evaluate_arg.extend(quote! {
let #arg_name = #arg
});
}
(arg_names, evaluate_arg)
};
let arg_names = (0..args.len())
.map(|i| format_ident!("arg_{}", i))
.collect::<Vec<_>>();
let future = match self.tp {
FnType::Fn(SelfType::Receiver { mutable: false, .. }) => {
let (arg_name, evaluate_arg) = evaluate_args();
quote! {{
#evaluate_arg;
#(let #arg_names = #args;)*
let __guard = #pyo3_path::impl_::coroutine::RefGuard::<#cls>::new(&#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf))?;
async move { function(&__guard, #(#arg_name),*).await }
async move { function(&__guard, #(#arg_names),*).await }
}}
}
FnType::Fn(SelfType::Receiver { mutable: true, .. }) => {
let (arg_name, evaluate_arg) = evaluate_args();
quote! {{
#evaluate_arg;
#(let #arg_names = #args;)*
let mut __guard = #pyo3_path::impl_::coroutine::RefMutGuard::<#cls>::new(&#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf))?;
async move { function(&mut __guard, #(#arg_name),*).await }
async move { function(&mut __guard, #(#arg_names),*).await }
}}
}
_ => {
let self_arg = self_arg();
if self_arg.is_empty() {
quote! { function(#(#args),*) }
} else {
let self_checker = holders.push_gil_refs_checker(self_arg.span());
if let Some(self_arg) = self_arg() {
quote! {
function(
// NB #self_arg includes a comma, so none inserted here
#pyo3_path::impl_::deprecations::inspect_type(#self_arg &#self_checker),
#self_arg
#(#args),*
)
}
} else {
quote! { function(#(#args),*) }
}
}
};
@ -584,22 +706,29 @@ impl<'a> FnSpec<'a> {
}};
}
call
} else {
let self_arg = self_arg();
if self_arg.is_empty() {
quote! { function(#(#args),*) }
} else {
let self_checker = holders.push_gil_refs_checker(self_arg.span());
} else if let Some(self_arg) = self_arg() {
quote! {
function(
// NB #self_arg includes a comma, so none inserted here
#pyo3_path::impl_::deprecations::inspect_type(#self_arg &#self_checker),
#self_arg
#(#args),*
)
}
}
} else {
quote! { function(#(#args),*) }
};
quotes::map_result_into_ptr(quotes::ok_wrap(call, ctx), ctx)
// We must assign the output_span to the return value of the call,
// but *not* of the call itself otherwise the spans get really weird
let ret_expr = quote! { let ret = #call; };
let ret_var = quote_spanned! {*output_span=> ret };
let return_conversion = quotes::map_result_into_ptr(quotes::ok_wrap(ret_var, ctx), ctx);
quote! {
{
#ret_expr
#return_conversion
}
}
};
let func_name = &self.name;
@ -609,6 +738,8 @@ impl<'a> FnSpec<'a> {
quote!(#func_name)
};
let deprecation = deprecate_trailing_option_default(self);
Ok(match self.convention {
CallingConvention::Noargs => {
let mut holders = Holders::new();
@ -616,40 +747,33 @@ impl<'a> FnSpec<'a> {
.signature
.arguments
.iter()
.map(|arg| {
if arg.py {
quote!(py)
} else if arg.is_cancel_handle {
quote!(__cancel_handle)
} else {
unreachable!()
}
.map(|arg| match arg {
FnArg::Py(..) => quote!(py),
FnArg::CancelHandle(..) => quote!(__cancel_handle),
_ => unreachable!("`CallingConvention::Noargs` should not contain any arguments (reaching Python) except for `self`, which is handled below."),
})
.collect();
let call = rust_call(args, &mut holders);
let check_gil_refs = holders.check_gil_refs();
let init_holders = holders.init_holders(ctx);
quote! {
unsafe fn #ident<'py>(
py: #pyo3_path::Python<'py>,
_slf: *mut #pyo3_path::ffi::PyObject,
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
#deprecation
let _slf_ref = &_slf;
let function = #rust_name; // Shadow the function name to avoid #3017
#init_holders
let result = #call;
#check_gil_refs
result
}
}
}
CallingConvention::Fastcall => {
let mut holders = Holders::new();
let (arg_convert, args) = impl_arg_params(self, cls, true, &mut holders, ctx)?;
let (arg_convert, args) = impl_arg_params(self, cls, true, &mut holders, ctx);
let call = rust_call(args, &mut holders);
let init_holders = holders.init_holders(ctx);
let check_gil_refs = holders.check_gil_refs();
quote! {
unsafe fn #ident<'py>(
@ -659,22 +783,21 @@ impl<'a> FnSpec<'a> {
_nargs: #pyo3_path::ffi::Py_ssize_t,
_kwnames: *mut #pyo3_path::ffi::PyObject
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
#deprecation
let _slf_ref = &_slf;
let function = #rust_name; // Shadow the function name to avoid #3017
#arg_convert
#init_holders
let result = #call;
#check_gil_refs
result
}
}
}
CallingConvention::Varargs => {
let mut holders = Holders::new();
let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx)?;
let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx);
let call = rust_call(args, &mut holders);
let init_holders = holders.init_holders(ctx);
let check_gil_refs = holders.check_gil_refs();
quote! {
unsafe fn #ident<'py>(
@ -683,25 +806,24 @@ impl<'a> FnSpec<'a> {
_args: *mut #pyo3_path::ffi::PyObject,
_kwargs: *mut #pyo3_path::ffi::PyObject
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
#deprecation
let _slf_ref = &_slf;
let function = #rust_name; // Shadow the function name to avoid #3017
#arg_convert
#init_holders
let result = #call;
#check_gil_refs
result
}
}
}
CallingConvention::TpNew => {
let mut holders = Holders::new();
let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx)?;
let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx);
let self_arg = self
.tp
.self_arg(cls, ExtractErrorMode::Raise, &mut holders, ctx);
let call = quote! { #rust_name(#self_arg #(#args),*) };
let call = quote_spanned! {*output_span=> #rust_name(#self_arg #(#args),*) };
let init_holders = holders.init_holders(ctx);
let check_gil_refs = holders.check_gil_refs();
quote! {
unsafe fn #ident(
py: #pyo3_path::Python<'_>,
@ -710,13 +832,13 @@ impl<'a> FnSpec<'a> {
_kwargs: *mut #pyo3_path::ffi::PyObject
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
use #pyo3_path::callback::IntoPyCallbackOutput;
#deprecation
let _slf_ref = &_slf;
let function = #rust_name; // Shadow the function name to avoid #3017
#arg_convert
#init_holders
let result = #call;
let initializer: #pyo3_path::PyClassInitializer::<#cls> = result.convert(py)?;
#check_gil_refs
#pyo3_path::impl_::pymethods::tp_new_impl(py, initializer, _slf)
}
}
@ -727,13 +849,13 @@ impl<'a> FnSpec<'a> {
/// Return a `PyMethodDef` constructor for this function, matching the selected
/// calling convention.
pub fn get_methoddef(&self, wrapper: impl ToTokens, doc: &PythonDoc, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path } = ctx;
let python_name = self.null_terminated_python_name();
let Ctx { pyo3_path, .. } = ctx;
let python_name = self.null_terminated_python_name(ctx);
match self.convention {
CallingConvention::Noargs => quote! {
#pyo3_path::impl_::pymethods::PyMethodDef::noargs(
#python_name,
#pyo3_path::impl_::pymethods::PyCFunction({
{
unsafe extern "C" fn trampoline(
_slf: *mut #pyo3_path::ffi::PyObject,
_args: *mut #pyo3_path::ffi::PyObject,
@ -746,14 +868,14 @@ impl<'a> FnSpec<'a> {
)
}
trampoline
}),
},
#doc,
)
},
CallingConvention::Fastcall => quote! {
#pyo3_path::impl_::pymethods::PyMethodDef::fastcall_cfunction_with_keywords(
#python_name,
#pyo3_path::impl_::pymethods::PyCFunctionFastWithKeywords({
{
unsafe extern "C" fn trampoline(
_slf: *mut #pyo3_path::ffi::PyObject,
_args: *const *mut #pyo3_path::ffi::PyObject,
@ -770,14 +892,14 @@ impl<'a> FnSpec<'a> {
)
}
trampoline
}),
},
#doc,
)
},
CallingConvention::Varargs => quote! {
#pyo3_path::impl_::pymethods::PyMethodDef::cfunction_with_keywords(
#python_name,
#pyo3_path::impl_::pymethods::PyCFunctionWithKeywords({
{
unsafe extern "C" fn trampoline(
_slf: *mut #pyo3_path::ffi::PyObject,
_args: *mut #pyo3_path::ffi::PyObject,
@ -792,7 +914,7 @@ impl<'a> FnSpec<'a> {
)
}
trampoline
}),
},
#doc,
)
},
@ -801,11 +923,11 @@ impl<'a> FnSpec<'a> {
}
/// Forwards to [utils::get_doc] with the text signature of this spec.
pub fn get_doc(&self, attrs: &[syn::Attribute]) -> PythonDoc {
pub fn get_doc(&self, attrs: &[syn::Attribute], ctx: &Ctx) -> PythonDoc {
let text_signature = self
.text_signature_call_signature()
.map(|sig| format!("{}{}", self.python_name, sig));
utils::get_doc(attrs, text_signature)
utils::get_doc(attrs, text_signature, ctx)
}
/// Creates the parenthesised arguments list for `__text_signature__` snippet based on this spec's signature
@ -854,10 +976,7 @@ impl MethodTypeAttribute {
/// If the attribute does not match one of the attribute names, returns `Ok(None)`.
///
/// Otherwise will either return a parse error or the attribute.
fn parse_if_matching_attribute(
attr: &syn::Attribute,
deprecations: &mut Deprecations<'_>,
) -> Result<Option<Self>> {
fn parse_if_matching_attribute(attr: &syn::Attribute) -> Result<Option<Self>> {
fn ensure_no_arguments(meta: &syn::Meta, ident: &str) -> syn::Result<()> {
match meta {
syn::Meta::Path(_) => Ok(()),
@ -901,11 +1020,6 @@ impl MethodTypeAttribute {
if path.is_ident("new") {
ensure_no_arguments(meta, "new")?;
Ok(Some(MethodTypeAttribute::New(path.span())))
} else if path.is_ident("__new__") {
let span = path.span();
deprecations.push(Deprecation::PyMethodsNewDeprecatedForm, span);
ensure_no_arguments(meta, "__new__")?;
Ok(Some(MethodTypeAttribute::New(span)))
} else if path.is_ident("classmethod") {
ensure_no_arguments(meta, "classmethod")?;
Ok(Some(MethodTypeAttribute::ClassMethod(path.span())))
@ -940,15 +1054,12 @@ impl Display for MethodTypeAttribute {
}
}
fn parse_method_attributes(
attrs: &mut Vec<syn::Attribute>,
deprecations: &mut Deprecations<'_>,
) -> Result<Vec<MethodTypeAttribute>> {
fn parse_method_attributes(attrs: &mut Vec<syn::Attribute>) -> Result<Vec<MethodTypeAttribute>> {
let mut new_attrs = Vec::new();
let mut found_attrs = Vec::new();
for attr in attrs.drain(..) {
match MethodTypeAttribute::parse_if_matching_attribute(&attr, deprecations)? {
match MethodTypeAttribute::parse_if_matching_attribute(&attr)? {
Some(attr) => found_attrs.push(attr),
None => new_attrs.push(attr),
}
@ -972,15 +1083,18 @@ fn ensure_signatures_on_valid_method(
if let Some(signature) = signature {
match fn_type {
FnType::Getter(_) => {
debug_assert!(!fn_type.signature_attribute_allowed());
bail_spanned!(signature.kw.span() => "`signature` not allowed with `getter`")
}
FnType::Setter(_) => {
debug_assert!(!fn_type.signature_attribute_allowed());
bail_spanned!(signature.kw.span() => "`signature` not allowed with `setter`")
}
FnType::ClassAttribute => {
debug_assert!(!fn_type.signature_attribute_allowed());
bail_spanned!(signature.kw.span() => "`signature` not allowed with `classattr`")
}
_ => {}
_ => debug_assert!(fn_type.signature_attribute_allowed()),
}
}
if let Some(text_signature) = text_signature {

View File

@ -1,82 +1,113 @@
//! Code generation for the function that initializes a python module and adds classes and function.
use crate::utils::Ctx;
use crate::{
attributes::{self, take_attributes, take_pyo3_options, CrateAttribute, NameAttribute},
attributes::{
self, kw, take_attributes, take_pyo3_options, CrateAttribute, ModuleAttribute,
NameAttribute, SubmoduleAttribute,
},
get_doc,
pyclass::PyClassPyO3Option,
pyfunction::{impl_wrap_pyfunction, PyFunctionOptions},
utils::{Ctx, LitCStr, PyO3CratePath},
};
use proc_macro2::TokenStream;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use std::ffi::CString;
use syn::{
ext::IdentExt,
parse::{Parse, ParseStream},
parse_quote, parse_quote_spanned,
punctuated::Punctuated,
spanned::Spanned,
token::Comma,
Item, Path, Result,
Item, Meta, Path, Result,
};
#[derive(Default)]
pub struct PyModuleOptions {
krate: Option<CrateAttribute>,
name: Option<syn::Ident>,
name: Option<NameAttribute>,
module: Option<ModuleAttribute>,
submodule: Option<kw::submodule>,
}
impl PyModuleOptions {
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
impl Parse for PyModuleOptions {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let mut options: PyModuleOptions = Default::default();
for option in take_pyo3_options(attrs)? {
match option {
PyModulePyO3Option::Name(name) => options.set_name(name.value.0)?,
PyModulePyO3Option::Crate(path) => options.set_crate(path)?,
}
}
options.add_attributes(
Punctuated::<PyModulePyO3Option, syn::Token![,]>::parse_terminated(input)?,
)?;
Ok(options)
}
}
fn set_name(&mut self, name: syn::Ident) -> Result<()> {
ensure_spanned!(
self.name.is_none(),
name.span() => "`name` may only be specified once"
);
self.name = Some(name);
Ok(())
impl PyModuleOptions {
fn take_pyo3_options(&mut self, attrs: &mut Vec<syn::Attribute>) -> Result<()> {
self.add_attributes(take_pyo3_options(attrs)?)
}
fn set_crate(&mut self, path: CrateAttribute) -> Result<()> {
fn add_attributes(
&mut self,
attrs: impl IntoIterator<Item = PyModulePyO3Option>,
) -> Result<()> {
macro_rules! set_option {
($key:ident $(, $extra:literal)?) => {
{
ensure_spanned!(
self.krate.is_none(),
path.span() => "`crate` may only be specified once"
self.$key.is_none(),
$key.span() => concat!("`", stringify!($key), "` may only be specified once" $(, $extra)?)
);
self.krate = Some(path);
self.$key = Some($key);
}
};
}
for attr in attrs {
match attr {
PyModulePyO3Option::Crate(krate) => set_option!(krate),
PyModulePyO3Option::Name(name) => set_option!(name),
PyModulePyO3Option::Module(module) => set_option!(module),
PyModulePyO3Option::Submodule(submodule) => set_option!(
submodule,
" (it is implicitly always specified for nested modules)"
),
}
}
Ok(())
}
}
pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
pub fn pymodule_module_impl(
module: &mut syn::ItemMod,
mut options: PyModuleOptions,
) -> Result<TokenStream> {
let syn::ItemMod {
attrs,
vis,
unsafety: _,
ident,
mod_token: _,
mod_token,
content,
semi: _,
} = &mut module;
} = module;
let items = if let Some((_, items)) = content {
items
} else {
bail_spanned!(module.span() => "`#[pymodule]` can only be used on inline modules")
bail_spanned!(mod_token.span() => "`#[pymodule]` can only be used on inline modules")
};
options.take_pyo3_options(attrs)?;
let ctx = &Ctx::new(&options.krate, None);
let Ctx { pyo3_path, .. } = ctx;
let doc = get_doc(attrs, None, ctx);
let name = options
.name
.map_or_else(|| ident.unraw(), |name| name.value.0);
let full_name = if let Some(module) = &options.module {
format!("{}.{}", module.value.value(), name)
} else {
name.to_string()
};
let options = PyModuleOptions::from_attrs(attrs)?;
let ctx = &Ctx::new(&options.krate);
let Ctx { pyo3_path } = ctx;
let doc = get_doc(attrs, None);
let mut module_items = Vec::new();
let mut module_items_cfg_attrs = Vec::new();
@ -143,7 +174,18 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
);
ensure_spanned!(pymodule_init.is_none(), item_fn.span() => "only one `#[pymodule_init]` may be specified");
pymodule_init = Some(quote! { #ident(module)?; });
} else if has_attribute(&item_fn.attrs, "pyfunction") {
} else if has_attribute(&item_fn.attrs, "pyfunction")
|| has_attribute_with_namespace(
&item_fn.attrs,
Some(pyo3_path),
&["pyfunction"],
)
|| has_attribute_with_namespace(
&item_fn.attrs,
Some(pyo3_path),
&["prelude", "pyfunction"],
)
{
module_items.push(ident.clone());
module_items_cfg_attrs.push(get_cfg_attributes(&item_fn.attrs));
}
@ -153,9 +195,27 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
!has_attribute(&item_struct.attrs, "pymodule_export"),
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
);
if has_attribute(&item_struct.attrs, "pyclass") {
if has_attribute(&item_struct.attrs, "pyclass")
|| has_attribute_with_namespace(
&item_struct.attrs,
Some(pyo3_path),
&["pyclass"],
)
|| has_attribute_with_namespace(
&item_struct.attrs,
Some(pyo3_path),
&["prelude", "pyclass"],
)
{
module_items.push(item_struct.ident.clone());
module_items_cfg_attrs.push(get_cfg_attributes(&item_struct.attrs));
if !has_pyo3_module_declared::<PyClassPyO3Option>(
&item_struct.attrs,
"pyclass",
|option| matches!(option, PyClassPyO3Option::Module(_)),
)? {
set_module_attribute(&mut item_struct.attrs, &full_name);
}
}
}
Item::Enum(item_enum) => {
@ -163,9 +223,23 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
!has_attribute(&item_enum.attrs, "pymodule_export"),
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
);
if has_attribute(&item_enum.attrs, "pyclass") {
if has_attribute(&item_enum.attrs, "pyclass")
|| has_attribute_with_namespace(&item_enum.attrs, Some(pyo3_path), &["pyclass"])
|| has_attribute_with_namespace(
&item_enum.attrs,
Some(pyo3_path),
&["prelude", "pyclass"],
)
{
module_items.push(item_enum.ident.clone());
module_items_cfg_attrs.push(get_cfg_attributes(&item_enum.attrs));
if !has_pyo3_module_declared::<PyClassPyO3Option>(
&item_enum.attrs,
"pyclass",
|option| matches!(option, PyClassPyO3Option::Module(_)),
)? {
set_module_attribute(&mut item_enum.attrs, &full_name);
}
}
}
Item::Mod(item_mod) => {
@ -173,9 +247,26 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
!has_attribute(&item_mod.attrs, "pymodule_export"),
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
);
if has_attribute(&item_mod.attrs, "pymodule") {
if has_attribute(&item_mod.attrs, "pymodule")
|| has_attribute_with_namespace(&item_mod.attrs, Some(pyo3_path), &["pymodule"])
|| has_attribute_with_namespace(
&item_mod.attrs,
Some(pyo3_path),
&["prelude", "pymodule"],
)
{
module_items.push(item_mod.ident.clone());
module_items_cfg_attrs.push(get_cfg_attributes(&item_mod.attrs));
if !has_pyo3_module_declared::<PyModulePyO3Option>(
&item_mod.attrs,
"pymodule",
|option| matches!(option, PyModulePyO3Option::Module(_)),
)? {
set_module_attribute(&mut item_mod.attrs, &full_name);
}
item_mod
.attrs
.push(parse_quote_spanned!(item_mod.mod_token.span()=> #[pyo3(submodule)]));
}
}
Item::ForeignMod(item) => {
@ -242,15 +333,7 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
}
}
let initialization = module_initialization(options, ident);
Ok(quote!(
#vis mod #ident {
#(#items)*
#initialization
impl MakeDef {
const fn make_def() -> #pyo3_path::impl_::pymodule::ModuleDef {
let module_def = quote! {{
use #pyo3_path::impl_::pymodule as impl_;
const INITIALIZER: impl_::ModuleInitializer = impl_::ModuleInitializer(__pyo3_pymodule);
unsafe {
@ -260,8 +343,15 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
INITIALIZER
)
}
}
}
}};
let initialization = module_initialization(&name, ctx, module_def, options.submodule.is_some());
Ok(quote!(
#(#attrs)*
#vis #mod_token #ident {
#(#items)*
#initialization
fn __pyo3_pymodule(module: &#pyo3_path::Bound<'_, #pyo3_path::types::PyModule>) -> #pyo3_path::PyResult<()> {
use #pyo3_path::impl_::pymodule::PyAddToModule;
@ -270,7 +360,7 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
#module_items::_PYO3_DEF.add_to_module(module)?;
)*
#pymodule_init
Ok(())
::std::result::Result::Ok(())
}
}
))
@ -278,17 +368,22 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
/// Generates the function that is called by the python interpreter to initialize the native
/// module
pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result<TokenStream> {
let options = PyModuleOptions::from_attrs(&mut function.attrs)?;
process_functions_in_module(&options, &mut function)?;
let ctx = &Ctx::new(&options.krate);
let stmts = std::mem::take(&mut function.block.stmts);
let Ctx { pyo3_path } = ctx;
pub fn pymodule_function_impl(
function: &mut syn::ItemFn,
mut options: PyModuleOptions,
) -> Result<TokenStream> {
options.take_pyo3_options(&mut function.attrs)?;
process_functions_in_module(&options, function)?;
let ctx = &Ctx::new(&options.krate, None);
let Ctx { pyo3_path, .. } = ctx;
let ident = &function.sig.ident;
let name = options
.name
.map_or_else(|| ident.unraw(), |name| name.value.0);
let vis = &function.vis;
let doc = get_doc(&function.attrs, None);
let doc = get_doc(&function.attrs, None, ctx);
let initialization = module_initialization(options, ident);
let initialization = module_initialization(&name, ctx, quote! { MakeDef::make_def() }, false);
// Module function called with optional Python<'_> marker as first arg, followed by the module.
let mut module_args = Vec::new();
@ -298,32 +393,8 @@ pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result<TokenStream>
module_args
.push(quote!(::std::convert::Into::into(#pyo3_path::impl_::pymethods::BoundRef(module))));
let extractors = function
.sig
.inputs
.iter()
.filter_map(|param| {
if let syn::FnArg::Typed(pat_type) = param {
if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
let ident: &syn::Ident = &pat_ident.ident;
return Some([
parse_quote!{ let check_gil_refs = #pyo3_path::impl_::deprecations::GilRefs::new(); },
parse_quote! { let #ident = #pyo3_path::impl_::deprecations::inspect_type(#ident, &check_gil_refs); },
parse_quote_spanned! { pat_type.span() => check_gil_refs.function_arg(); },
]);
}
}
None
})
.flatten();
function.block.stmts = extractors.chain(stmts).collect();
function
.attrs
.push(parse_quote!(#[allow(clippy::used_underscore_binding)]));
Ok(quote! {
#function
#[doc(hidden)]
#vis mod #ident {
#initialization
}
@ -332,6 +403,7 @@ pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result<TokenStream>
// this avoids complications around the fact that the generated module has a different scope
// (and `super` doesn't always refer to the outer scope, e.g. if the `#[pymodule] is
// inside a function body)
#[allow(unknown_lints, non_local_definitions)]
impl #ident::MakeDef {
const fn make_def() -> #pyo3_path::impl_::pymodule::ModuleDef {
fn __pyo3_pymodule(module: &#pyo3_path::Bound<'_, #pyo3_path::types::PyModule>) -> #pyo3_path::PyResult<()> {
@ -351,20 +423,27 @@ pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result<TokenStream>
})
}
fn module_initialization(options: PyModuleOptions, ident: &syn::Ident) -> TokenStream {
let name = options.name.unwrap_or_else(|| ident.unraw());
let ctx = &Ctx::new(&options.krate);
let Ctx { pyo3_path } = ctx;
fn module_initialization(
name: &syn::Ident,
ctx: &Ctx,
module_def: TokenStream,
is_submodule: bool,
) -> TokenStream {
let Ctx { pyo3_path, .. } = ctx;
let pyinit_symbol = format!("PyInit_{}", name);
let name = name.to_string();
let pyo3_name = LitCStr::new(CString::new(name).unwrap(), Span::call_site(), ctx);
quote! {
let mut result = quote! {
#[doc(hidden)]
pub const __PYO3_NAME: &'static str = concat!(stringify!(#name), "\0");
pub const __PYO3_NAME: &'static ::std::ffi::CStr = #pyo3_name;
pub(super) struct MakeDef;
#[doc(hidden)]
pub static _PYO3_DEF: #pyo3_path::impl_::pymodule::ModuleDef = MakeDef::make_def();
pub static _PYO3_DEF: #pyo3_path::impl_::pymodule::ModuleDef = #module_def;
};
if !is_submodule {
result.extend(quote! {
/// This autogenerated function is called by the python interpreter when importing
/// the module.
#[doc(hidden)]
@ -372,17 +451,21 @@ fn module_initialization(options: PyModuleOptions, ident: &syn::Ident) -> TokenS
pub unsafe extern "C" fn __pyo3_init() -> *mut #pyo3_path::ffi::PyObject {
#pyo3_path::impl_::trampoline::module_init(|py| _PYO3_DEF.make_module(py))
}
});
}
result
}
/// Finds and takes care of the #[pyfn(...)] in `#[pymodule]`
fn process_functions_in_module(options: &PyModuleOptions, func: &mut syn::ItemFn) -> Result<()> {
let ctx = &Ctx::new(&options.krate);
let Ctx { pyo3_path } = ctx;
let mut stmts: Vec<syn::Stmt> = vec![syn::parse_quote!(
#[allow(unknown_lints, unused_imports, redundant_imports)]
use #pyo3_path::{PyNativeType, types::PyModuleMethods};
)];
let ctx = &Ctx::new(&options.krate, None);
let Ctx { pyo3_path, .. } = ctx;
let mut stmts: Vec<syn::Stmt> = Vec::new();
#[cfg(feature = "gil-refs")]
let imports = quote!(use #pyo3_path::{PyNativeType, types::PyModuleMethods};);
#[cfg(not(feature = "gil-refs"))]
let imports = quote!(use #pyo3_path::types::PyModuleMethods;);
for mut stmt in func.block.stmts.drain(..) {
if let syn::Stmt::Item(Item::Fn(func)) = &mut stmt {
@ -392,7 +475,11 @@ fn process_functions_in_module(options: &PyModuleOptions, func: &mut syn::ItemFn
let name = &func.sig.ident;
let statements: Vec<syn::Stmt> = syn::parse_quote! {
#wrapped_function
{
#[allow(unknown_lints, unused_imports, redundant_imports)]
#imports
#module_name.as_borrowed().add_function(#pyo3_path::wrap_pyfunction!(#name, #module_name.as_borrowed())?)?;
}
};
stmts.extend(statements);
}
@ -478,13 +565,78 @@ fn find_and_remove_attribute(attrs: &mut Vec<syn::Attribute>, ident: &str) -> bo
found
}
enum IdentOrStr<'a> {
Str(&'a str),
Ident(syn::Ident),
}
impl<'a> PartialEq<syn::Ident> for IdentOrStr<'a> {
fn eq(&self, other: &syn::Ident) -> bool {
match self {
IdentOrStr::Str(s) => other == s,
IdentOrStr::Ident(i) => other == i,
}
}
}
fn has_attribute(attrs: &[syn::Attribute], ident: &str) -> bool {
attrs.iter().any(|attr| attr.path().is_ident(ident))
has_attribute_with_namespace(attrs, None, &[ident])
}
fn has_attribute_with_namespace(
attrs: &[syn::Attribute],
crate_path: Option<&PyO3CratePath>,
idents: &[&str],
) -> bool {
let mut segments = vec![];
if let Some(c) = crate_path {
match c {
PyO3CratePath::Given(paths) => {
for p in &paths.segments {
segments.push(IdentOrStr::Ident(p.ident.clone()));
}
}
PyO3CratePath::Default => segments.push(IdentOrStr::Str("pyo3")),
}
};
for i in idents {
segments.push(IdentOrStr::Str(i));
}
attrs.iter().any(|attr| {
segments
.iter()
.eq(attr.path().segments.iter().map(|v| &v.ident))
})
}
fn set_module_attribute(attrs: &mut Vec<syn::Attribute>, module_name: &str) {
attrs.push(parse_quote!(#[pyo3(module = #module_name)]));
}
fn has_pyo3_module_declared<T: Parse>(
attrs: &[syn::Attribute],
root_attribute_name: &str,
is_module_option: impl Fn(&T) -> bool + Copy,
) -> Result<bool> {
for attr in attrs {
if (attr.path().is_ident("pyo3") || attr.path().is_ident(root_attribute_name))
&& matches!(attr.meta, Meta::List(_))
{
for option in &attr.parse_args_with(Punctuated::<T, Comma>::parse_terminated)? {
if is_module_option(option) {
return Ok(true);
}
}
}
}
Ok(false)
}
enum PyModulePyO3Option {
Submodule(SubmoduleAttribute),
Crate(CrateAttribute),
Name(NameAttribute),
Module(ModuleAttribute),
}
impl Parse for PyModulePyO3Option {
@ -494,6 +646,10 @@ impl Parse for PyModulePyO3Option {
input.parse().map(PyModulePyO3Option::Name)
} else if lookahead.peek(syn::Token![crate]) {
input.parse().map(PyModulePyO3Option::Crate)
} else if lookahead.peek(attributes::kw::module) {
input.parse().map(PyModulePyO3Option::Module)
} else if lookahead.peek(attributes::kw::submodule) {
input.parse().map(PyModulePyO3Option::Submodule)
} else {
Err(lookahead.error())
}

View File

@ -1,24 +1,21 @@
use crate::utils::Ctx;
use crate::{
method::{FnArg, FnSpec},
method::{FnArg, FnSpec, RegularArg},
pyfunction::FunctionSignature,
quotes::some_wrap,
};
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use quote::{format_ident, quote, quote_spanned};
use syn::spanned::Spanned;
use syn::Result;
pub struct Holders {
holders: Vec<syn::Ident>,
gil_refs_checkers: Vec<syn::Ident>,
}
impl Holders {
pub fn new() -> Self {
Holders {
holders: Vec::new(),
gil_refs_checkers: Vec::new(),
}
}
@ -28,71 +25,33 @@ impl Holders {
holder
}
pub fn push_gil_refs_checker(&mut self, span: Span) -> syn::Ident {
let gil_refs_checker = syn::Ident::new(
&format!("gil_refs_checker_{}", self.gil_refs_checkers.len()),
span,
);
self.gil_refs_checkers.push(gil_refs_checker.clone());
gil_refs_checker
}
pub fn init_holders(&self, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
let holders = &self.holders;
let gil_refs_checkers = &self.gil_refs_checkers;
quote! {
#[allow(clippy::let_unit_value)]
#(let mut #holders = #pyo3_path::impl_::extract_argument::FunctionArgumentHolder::INIT;)*
#(let #gil_refs_checkers = #pyo3_path::impl_::deprecations::GilRefs::new();)*
}
}
pub fn check_gil_refs(&self) -> TokenStream {
self.gil_refs_checkers
.iter()
.map(|e| quote_spanned! { e.span() => #e.function_arg(); })
.collect()
}
}
/// Return true if the argument list is simply (*args, **kwds).
pub fn is_forwarded_args(signature: &FunctionSignature<'_>) -> bool {
matches!(
signature.arguments.as_slice(),
[
FnArg {
is_varargs: true,
..
},
FnArg {
is_kwargs: true,
..
},
]
[FnArg::VarArgs(..), FnArg::KwArgs(..),]
)
}
pub(crate) fn check_arg_for_gil_refs(
tokens: TokenStream,
gil_refs_checker: syn::Ident,
ctx: &Ctx,
) -> TokenStream {
let Ctx { pyo3_path } = ctx;
quote! {
#pyo3_path::impl_::deprecations::inspect_type(#tokens, &#gil_refs_checker)
}
}
pub fn impl_arg_params(
spec: &FnSpec<'_>,
self_: Option<&syn::Type>,
fastcall: bool,
holders: &mut Holders,
ctx: &Ctx,
) -> Result<(TokenStream, Vec<TokenStream>)> {
) -> (TokenStream, Vec<TokenStream>) {
let args_array = syn::Ident::new("output", Span::call_site());
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
let from_py_with = spec
.signature
@ -100,13 +59,10 @@ pub fn impl_arg_params(
.iter()
.enumerate()
.filter_map(|(i, arg)| {
let from_py_with = &arg.attrs.from_py_with.as_ref()?.value;
let from_py_with_holder =
syn::Ident::new(&format!("from_py_with_{}", i), Span::call_site());
let from_py_with = &arg.from_py_with()?.value;
let from_py_with_holder = format_ident!("from_py_with_{}", i);
Some(quote_spanned! { from_py_with.span() =>
let e = #pyo3_path::impl_::deprecations::GilRefs::new();
let #from_py_with_holder = #pyo3_path::impl_::deprecations::inspect_fn(#from_py_with, &e);
e.from_py_with_arg();
let #from_py_with_holder = #from_py_with;
})
})
.collect::<TokenStream>();
@ -119,28 +75,16 @@ pub fn impl_arg_params(
.arguments
.iter()
.enumerate()
.map(|(i, arg)| {
let from_py_with =
syn::Ident::new(&format!("from_py_with_{}", i), Span::call_site());
let arg_value = quote!(#args_array[0].as_deref());
impl_arg_param(arg, from_py_with, arg_value, holders, ctx).map(|tokens| {
check_arg_for_gil_refs(
tokens,
holders.push_gil_refs_checker(arg.ty.span()),
ctx,
)
})
})
.collect::<Result<_>>()?;
return Ok((
.map(|(i, arg)| impl_arg_param(arg, i, &mut 0, holders, ctx))
.collect();
return (
quote! {
let _args = #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &_args);
let _kwargs = #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr_or_opt(py, &_kwargs);
#from_py_with
},
arg_convert,
));
);
};
let positional_parameter_names = &spec.signature.python_signature.positional_parameters;
@ -171,18 +115,8 @@ pub fn impl_arg_params(
.arguments
.iter()
.enumerate()
.map(|(i, arg)| {
let from_py_with = syn::Ident::new(&format!("from_py_with_{}", i), Span::call_site());
let arg_value = quote!(#args_array[#option_pos].as_deref());
if arg.is_regular() {
option_pos += 1;
}
impl_arg_param(arg, from_py_with, arg_value, holders, ctx).map(|tokens| {
check_arg_for_gil_refs(tokens, holders.push_gil_refs_checker(arg.ty.span()), ctx)
})
})
.collect::<Result<_>>()?;
.map(|(i, arg)| impl_arg_param(arg, i, &mut option_pos, holders, ctx))
.collect();
let args_handler = if spec.signature.python_signature.varargs.is_some() {
quote! { #pyo3_path::impl_::extract_argument::TupleVarargs }
@ -224,7 +158,7 @@ pub fn impl_arg_params(
};
// create array of arguments, and then parse
Ok((
(
quote! {
const DESCRIPTION: #pyo3_path::impl_::extract_argument::FunctionDescription = #pyo3_path::impl_::extract_argument::FunctionDescription {
cls_name: #cls_name,
@ -239,19 +173,64 @@ pub fn impl_arg_params(
#from_py_with
},
param_conversion,
))
)
}
fn impl_arg_param(
arg: &FnArg<'_>,
pos: usize,
option_pos: &mut usize,
holders: &mut Holders,
ctx: &Ctx,
) -> TokenStream {
let Ctx { pyo3_path, .. } = ctx;
let args_array = syn::Ident::new("output", Span::call_site());
match arg {
FnArg::Regular(arg) => {
let from_py_with = format_ident!("from_py_with_{}", pos);
let arg_value = quote!(#args_array[#option_pos].as_deref());
*option_pos += 1;
impl_regular_arg_param(arg, from_py_with, arg_value, holders, ctx)
}
FnArg::VarArgs(arg) => {
let holder = holders.push_holder(arg.name.span());
let name_str = arg.name.to_string();
quote_spanned! { arg.name.span() =>
#pyo3_path::impl_::extract_argument::extract_argument(
&_args,
&mut #holder,
#name_str
)?
}
}
FnArg::KwArgs(arg) => {
let holder = holders.push_holder(arg.name.span());
let name_str = arg.name.to_string();
quote_spanned! { arg.name.span() =>
#pyo3_path::impl_::extract_argument::extract_optional_argument(
_kwargs.as_deref(),
&mut #holder,
#name_str,
|| ::std::option::Option::None
)?
}
}
FnArg::Py(..) => quote! { py },
FnArg::CancelHandle(..) => quote! { __cancel_handle },
}
}
/// Re option_pos: The option slice doesn't contain the py: Python argument, so the argument
/// index and the index in option diverge when using py: Python
pub(crate) fn impl_arg_param(
arg: &FnArg<'_>,
pub(crate) fn impl_regular_arg_param(
arg: &RegularArg<'_>,
from_py_with: syn::Ident,
arg_value: TokenStream, // expected type: Option<&'a Bound<'py, PyAny>>
holders: &mut Holders,
ctx: &Ctx,
) -> Result<TokenStream> {
let Ctx { pyo3_path } = ctx;
) -> TokenStream {
let Ctx { pyo3_path, .. } = ctx;
let pyo3_path = pyo3_path.to_tokens_spanned(arg.ty.span());
// Use this macro inside this function, to ensure that all code generated here is associated
@ -260,64 +239,19 @@ pub(crate) fn impl_arg_param(
($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => $($tokens)*) }
}
if arg.py {
return Ok(quote! { py });
}
if arg.is_cancel_handle {
return Ok(quote! { __cancel_handle });
}
let name = arg.name;
let name_str = name.to_string();
if arg.is_varargs {
ensure_spanned!(
arg.optional.is_none(),
arg.name.span() => "args cannot be optional"
);
let holder = holders.push_holder(arg.ty.span());
return Ok(quote_arg_span! {
#pyo3_path::impl_::extract_argument::extract_argument(
&_args,
&mut #holder,
#name_str
)?
});
} else if arg.is_kwargs {
ensure_spanned!(
arg.optional.is_some(),
arg.name.span() => "kwargs must be Option<_>"
);
let holder = holders.push_holder(arg.name.span());
return Ok(quote_arg_span! {
#pyo3_path::impl_::extract_argument::extract_optional_argument(
_kwargs.as_deref(),
&mut #holder,
#name_str,
|| ::std::option::Option::None
)?
});
}
let mut default = arg.default.as_ref().map(|expr| quote!(#expr));
let name_str = arg.name.to_string();
let mut default = arg.default_value.as_ref().map(|expr| quote!(#expr));
// Option<T> arguments have special treatment: the default should be specified _without_ the
// Some() wrapper. Maybe this should be changed in future?!
if arg.optional.is_some() {
if arg.option_wrapped_type.is_some() {
default = Some(default.map_or_else(
|| quote!(::std::option::Option::None),
|tokens| some_wrap(tokens, ctx),
));
}
let tokens = if arg
.attrs
.from_py_with
.as_ref()
.map(|attr| &attr.value)
.is_some()
{
if arg.from_py_with.is_some() {
if let Some(default) = default {
quote_arg_span! {
#pyo3_path::impl_::extract_argument::from_py_with_with_default(
@ -339,7 +273,7 @@ pub(crate) fn impl_arg_param(
)?
}
}
} else if arg.optional.is_some() {
} else if arg.option_wrapped_type.is_some() {
let holder = holders.push_holder(arg.name.span());
quote_arg_span! {
#pyo3_path::impl_::extract_argument::extract_optional_argument(
@ -374,7 +308,5 @@ pub(crate) fn impl_arg_param(
#name_str
)?
}
};
Ok(tokens)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,6 @@ use crate::{
self, get_pyo3_options, take_attributes, take_pyo3_options, CrateAttribute,
FromPyWithAttribute, NameAttribute, TextSignatureAttribute,
},
deprecations::Deprecations,
method::{self, CallingConvention, FnArg},
pymethod::check_generic,
};
@ -18,7 +17,7 @@ use syn::{
mod signature;
pub use self::signature::{FunctionSignature, SignatureAttribute};
pub use self::signature::{ConstructorAttribute, FunctionSignature, SignatureAttribute};
#[derive(Clone, Debug)]
pub struct PyFunctionArgPyO3Attributes {
@ -205,10 +204,13 @@ pub fn impl_wrap_pyfunction(
krate,
} = options;
let ctx = &Ctx::new(&krate);
let Ctx { pyo3_path } = &ctx;
let ctx = &Ctx::new(&krate, Some(&func.sig));
let Ctx { pyo3_path, .. } = &ctx;
let python_name = name.map_or_else(|| func.sig.ident.unraw(), |name| name.value.0);
let python_name = name
.as_ref()
.map_or_else(|| &func.sig.ident, |name| &name.value.0)
.unraw();
let tp = if pass_module.is_some() {
let span = match func.sig.inputs.first() {
@ -249,7 +251,6 @@ pub fn impl_wrap_pyfunction(
text_signature,
asyncness: func.sig.asyncness,
unsafety: func.sig.unsafety,
deprecations: Deprecations::new(ctx),
};
let vis = &func.vis;
@ -257,7 +258,7 @@ pub fn impl_wrap_pyfunction(
let wrapper_ident = format_ident!("__pyfunction_{}", spec.name);
let wrapper = spec.get_wrapper_function(&wrapper_ident, None, ctx)?;
let methoddef = spec.get_methoddef(wrapper_ident, &spec.get_doc(&func.attrs), ctx);
let methoddef = spec.get_methoddef(wrapper_ident, &spec.get_doc(&func.attrs, ctx), ctx);
let wrapped_pyfunction = quote! {
@ -273,6 +274,7 @@ pub fn impl_wrap_pyfunction(
// this avoids complications around the fact that the generated module has a different scope
// (and `super` doesn't always refer to the outer scope, e.g. if the `#[pyfunction] is
// inside a function body)
#[allow(unknown_lints, non_local_definitions)]
impl #name::MakeDef {
const _PYO3_DEF: #pyo3_path::impl_::pymethods::PyMethodDef = #methoddef;
}

View File

@ -10,9 +10,10 @@ use syn::{
use crate::{
attributes::{kw, KeywordAttribute},
method::FnArg,
method::{FnArg, RegularArg},
};
#[derive(Clone)]
pub struct Signature {
paren_token: syn::token::Paren,
pub items: Punctuated<SignatureItem, Token![,]>,
@ -36,35 +37,35 @@ impl ToTokens for Signature {
}
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignatureItemArgument {
pub ident: syn::Ident,
pub eq_and_default: Option<(Token![=], syn::Expr)>,
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignatureItemPosargsSep {
pub slash: Token![/],
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignatureItemVarargsSep {
pub asterisk: Token![*],
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignatureItemVarargs {
pub sep: SignatureItemVarargsSep,
pub ident: syn::Ident,
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignatureItemKwargs {
pub asterisks: (Token![*], Token![*]),
pub ident: syn::Ident,
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SignatureItem {
Argument(Box<SignatureItemArgument>),
PosargsSep(SignatureItemPosargsSep),
@ -195,6 +196,16 @@ impl ToTokens for SignatureItemPosargsSep {
}
pub type SignatureAttribute = KeywordAttribute<kw::signature, Signature>;
pub type ConstructorAttribute = KeywordAttribute<kw::constructor, Signature>;
impl ConstructorAttribute {
pub fn into_signature(self) -> SignatureAttribute {
SignatureAttribute {
kw: kw::signature(self.kw.span),
value: self.value,
}
}
}
#[derive(Default)]
pub struct PythonSignature {
@ -351,37 +362,40 @@ impl<'a> FunctionSignature<'a> {
let mut next_non_py_argument_checked = |name: &syn::Ident| {
for fn_arg in args_iter.by_ref() {
if fn_arg.py {
match fn_arg {
crate::method::FnArg::Py(..) => {
// If the user incorrectly tried to include py: Python in the
// signature, give a useful error as a hint.
ensure_spanned!(
name != fn_arg.name,
name != fn_arg.name(),
name.span() => "arguments of type `Python` must not be part of the signature"
);
// Otherwise try next argument.
continue;
}
if fn_arg.is_cancel_handle {
crate::method::FnArg::CancelHandle(..) => {
// If the user incorrectly tried to include cancel: CoroutineCancel in the
// signature, give a useful error as a hint.
ensure_spanned!(
name != fn_arg.name,
name != fn_arg.name(),
name.span() => "`cancel_handle` argument must not be part of the signature"
);
// Otherwise try next argument.
continue;
}
_ => {
ensure_spanned!(
name == fn_arg.name,
name == fn_arg.name(),
name.span() => format!(
"expected argument from function definition `{}` but got argument `{}`",
fn_arg.name.unraw(),
fn_arg.name().unraw(),
name.unraw(),
)
);
return Ok(fn_arg);
}
}
}
bail_spanned!(
name.span() => "signature entry does not have a corresponding function argument"
)
@ -398,7 +412,15 @@ impl<'a> FunctionSignature<'a> {
arg.span(),
)?;
if let Some((_, default)) = &arg.eq_and_default {
fn_arg.default = Some(default.clone());
if let FnArg::Regular(arg) = fn_arg {
arg.default_value = Some(default.clone());
} else {
unreachable!(
"`Python` and `CancelHandle` are already handled above and `*args`/`**kwargs` are \
parsed and transformed below. Because the have to come last and are only allowed \
once, this has to be a regular argument."
);
}
}
}
SignatureItem::VarargsSep(sep) => {
@ -406,12 +428,12 @@ impl<'a> FunctionSignature<'a> {
}
SignatureItem::Varargs(varargs) => {
let fn_arg = next_non_py_argument_checked(&varargs.ident)?;
fn_arg.is_varargs = true;
fn_arg.to_varargs_mut()?;
parse_state.add_varargs(&mut python_signature, varargs)?;
}
SignatureItem::Kwargs(kwargs) => {
let fn_arg = next_non_py_argument_checked(&kwargs.ident)?;
fn_arg.is_kwargs = true;
fn_arg.to_kwargs_mut()?;
parse_state.add_kwargs(&mut python_signature, kwargs)?;
}
SignatureItem::PosargsSep(sep) => {
@ -421,9 +443,11 @@ impl<'a> FunctionSignature<'a> {
}
// Ensure no non-py arguments remain
if let Some(arg) = args_iter.find(|arg| !arg.py && !arg.is_cancel_handle) {
if let Some(arg) =
args_iter.find(|arg| !matches!(arg, FnArg::Py(..) | FnArg::CancelHandle(..)))
{
bail_spanned!(
attribute.kw.span() => format!("missing signature entry for argument `{}`", arg.name)
attribute.kw.span() => format!("missing signature entry for argument `{}`", arg.name())
);
}
@ -439,15 +463,20 @@ impl<'a> FunctionSignature<'a> {
let mut python_signature = PythonSignature::default();
for arg in &arguments {
// Python<'_> arguments don't show in Python signature
if arg.py || arg.is_cancel_handle {
if matches!(arg, FnArg::Py(..) | FnArg::CancelHandle(..)) {
continue;
}
if arg.optional.is_none() {
if let FnArg::Regular(RegularArg {
ty,
option_wrapped_type: None,
..
}) = arg
{
// This argument is required, all previous arguments must also have been required
ensure_spanned!(
python_signature.required_positional_parameters == python_signature.positional_parameters.len(),
arg.ty.span() => "required arguments after an `Option<_>` argument are ambiguous\n\
ty.span() => "required arguments after an `Option<_>` argument are ambiguous\n\
= help: add a `#[pyo3(signature)]` annotation on this function to unambiguously specify the default values for all optional parameters"
);
@ -457,7 +486,7 @@ impl<'a> FunctionSignature<'a> {
python_signature
.positional_parameters
.push(arg.name.unraw().to_string());
.push(arg.name().unraw().to_string());
}
Ok(Self {
@ -469,8 +498,12 @@ impl<'a> FunctionSignature<'a> {
fn default_value_for_parameter(&self, parameter: &str) -> String {
let mut default = "...".to_string();
if let Some(fn_arg) = self.arguments.iter().find(|arg| arg.name == parameter) {
if let Some(arg_default) = fn_arg.default.as_ref() {
if let Some(fn_arg) = self.arguments.iter().find(|arg| arg.name() == parameter) {
if let FnArg::Regular(RegularArg {
default_value: Some(arg_default),
..
}) = fn_arg
{
match arg_default {
// literal values
syn::Expr::Lit(syn::ExprLit { lit, .. }) => match lit {
@ -496,7 +529,11 @@ impl<'a> FunctionSignature<'a> {
// others, unsupported yet so defaults to `...`
_ => {}
}
} else if fn_arg.optional.is_some() {
} else if let FnArg::Regular(RegularArg {
option_wrapped_type: Some(..),
..
}) = fn_arg
{
// functions without a `#[pyo3(signature = (...))]` option
// will treat trailing `Option<T>` arguments as having a default of `None`
default = "None".to_string();

View File

@ -90,7 +90,6 @@ pub fn impl_methods(
methods_type: PyClassMethodsType,
options: PyImplOptions,
) -> syn::Result<TokenStream> {
let ctx = &Ctx::new(&options.krate);
let mut trait_impls = Vec::new();
let mut proto_impls = Vec::new();
let mut methods = Vec::new();
@ -101,6 +100,7 @@ pub fn impl_methods(
for iimpl in impls {
match iimpl {
syn::ImplItem::Fn(meth) => {
let ctx = &Ctx::new(&options.krate, Some(&meth.sig));
let mut fun_options = PyFunctionOptions::from_attrs(&mut meth.attrs)?;
fun_options.krate = fun_options.krate.or_else(|| options.krate.clone());
match pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs, fun_options, ctx)?
@ -129,7 +129,8 @@ pub fn impl_methods(
}
}
syn::ImplItem::Const(konst) => {
let attributes = ConstAttributes::from_attrs(&mut konst.attrs, ctx)?;
let ctx = &Ctx::new(&options.krate, None);
let attributes = ConstAttributes::from_attrs(&mut konst.attrs)?;
if attributes.is_class_attr {
let spec = ConstSpec {
rust_ident: konst.ident.clone(),
@ -159,11 +160,10 @@ pub fn impl_methods(
_ => {}
}
}
let ctx = &Ctx::new(&options.krate, None);
add_shared_proto_slots(ty, &mut proto_impls, implemented_proto_fragments, ctx);
let ctx = &Ctx::new(&options.krate);
let items = match methods_type {
PyClassMethodsType::Specialization => impl_py_methods(ty, methods, proto_impls, ctx),
PyClassMethodsType::Inventory => submit_methods_inventory(ty, methods, proto_impls, ctx),
@ -182,27 +182,27 @@ pub fn impl_methods(
})
}
pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec<'_>, ctx: &Ctx) -> MethodAndMethodDef {
pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec, ctx: &Ctx) -> MethodAndMethodDef {
let member = &spec.rust_ident;
let wrapper_ident = format_ident!("__pymethod_{}__", member);
let deprecations = &spec.attributes.deprecations;
let python_name = &spec.null_terminated_python_name();
let Ctx { pyo3_path } = ctx;
let python_name = spec.null_terminated_python_name(ctx);
let Ctx { pyo3_path, .. } = ctx;
let associated_method = quote! {
fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> {
#deprecations
::std::result::Result::Ok(#pyo3_path::IntoPy::into_py(#cls::#member, py))
}
};
let method_def = quote! {
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
#pyo3_path::class::PyMethodDefType::ClassAttribute({
#pyo3_path::class::PyClassAttributeDef::new(
#python_name,
#pyo3_path::impl_::pymethods::PyClassAttributeFactory(#cls::#wrapper_ident)
#cls::#wrapper_ident
)
})
)
};
MethodAndMethodDef {
@ -217,8 +217,9 @@ fn impl_py_methods(
proto_impls: Vec<TokenStream>,
ctx: &Ctx,
) -> TokenStream {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
quote! {
#[allow(unknown_lints, non_local_definitions)]
impl #pyo3_path::impl_::pyclass::PyMethods<#ty>
for #pyo3_path::impl_::pyclass::PyClassImplCollector<#ty>
{
@ -239,7 +240,7 @@ fn add_shared_proto_slots(
mut implemented_proto_fragments: HashSet<String>,
ctx: &Ctx,
) {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
macro_rules! try_add_shared_slot {
($slot:ident, $($fragments:literal),*) => {{
let mut implemented = false;
@ -297,7 +298,7 @@ fn submit_methods_inventory(
proto_impls: Vec<TokenStream>,
ctx: &Ctx,
) -> TokenStream {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
quote! {
#pyo3_path::inventory::submit! {
type Inventory = <#ty as #pyo3_path::impl_::pyclass::PyClassImpl>::Inventory;

View File

@ -1,10 +1,12 @@
use std::borrow::Cow;
use std::ffi::CString;
use crate::attributes::{NameAttribute, RenamingRule};
use crate::method::{CallingConvention, ExtractErrorMode};
use crate::params::{check_arg_for_gil_refs, impl_arg_param, Holders};
use crate::utils::Ctx;
use crate::deprecations::deprecate_trailing_option_default;
use crate::method::{CallingConvention, ExtractErrorMode, PyArg};
use crate::params::{impl_regular_arg_param, Holders};
use crate::utils::PythonDoc;
use crate::utils::{Ctx, LitCStr};
use crate::{
method::{FnArg, FnSpec, FnType, SelfType},
pyfunction::PyFunctionOptions,
@ -162,9 +164,8 @@ impl<'a> PyMethod<'a> {
sig: &'a mut syn::Signature,
meth_attrs: &mut Vec<syn::Attribute>,
options: PyFunctionOptions,
ctx: &'a Ctx,
) -> Result<Self> {
let spec = FnSpec::parse(sig, meth_attrs, options, ctx)?;
let spec = FnSpec::parse(sig, meth_attrs, options)?;
let method_name = spec.python_name.to_string();
let kind = PyMethodKind::from_name(&method_name);
@ -193,9 +194,9 @@ pub fn gen_py_method(
) -> Result<GeneratedPyMethod> {
check_generic(sig)?;
ensure_function_options_valid(&options)?;
let method = PyMethod::parse(sig, meth_attrs, options, ctx)?;
let method = PyMethod::parse(sig, meth_attrs, options)?;
let spec = &method.spec;
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
Ok(match (method.kind, &spec.tp) {
// Class attributes go before protos so that class attributes can be used to set proto
@ -226,21 +227,21 @@ pub fn gen_py_method(
(_, FnType::Fn(_)) => GeneratedPyMethod::Method(impl_py_method_def(
cls,
spec,
&spec.get_doc(meth_attrs),
&spec.get_doc(meth_attrs, ctx),
None,
ctx,
)?),
(_, FnType::FnClass(_)) => GeneratedPyMethod::Method(impl_py_method_def(
cls,
spec,
&spec.get_doc(meth_attrs),
&spec.get_doc(meth_attrs, ctx),
Some(quote!(#pyo3_path::ffi::METH_CLASS)),
ctx,
)?),
(_, FnType::FnStatic) => GeneratedPyMethod::Method(impl_py_method_def(
cls,
spec,
&spec.get_doc(meth_attrs),
&spec.get_doc(meth_attrs, ctx),
Some(quote!(#pyo3_path::ffi::METH_STATIC)),
ctx,
)?),
@ -254,7 +255,7 @@ pub fn gen_py_method(
PropertyType::Function {
self_type,
spec,
doc: spec.get_doc(meth_attrs),
doc: spec.get_doc(meth_attrs, ctx),
},
ctx,
)?),
@ -263,7 +264,7 @@ pub fn gen_py_method(
PropertyType::Function {
self_type,
spec,
doc: spec.get_doc(meth_attrs),
doc: spec.get_doc(meth_attrs, ctx),
},
ctx,
)?),
@ -317,7 +318,7 @@ pub fn impl_py_method_def(
flags: Option<TokenStream>,
ctx: &Ctx,
) -> Result<MethodAndMethodDef> {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
let wrapper_ident = format_ident!("__pymethod_{}__", spec.python_name);
let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?;
let add_flags = flags.map(|flags| quote!(.flags(#flags)));
@ -328,7 +329,9 @@ pub fn impl_py_method_def(
};
let methoddef = spec.get_methoddef(quote! { #cls::#wrapper_ident }, doc, ctx);
let method_def = quote! {
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
#pyo3_path::class::PyMethodDefType::#methoddef_type(#methoddef #add_flags)
)
};
Ok(MethodAndMethodDef {
associated_method,
@ -342,7 +345,7 @@ pub fn impl_py_method_def_new(
spec: &FnSpec<'_>,
ctx: &Ctx,
) -> Result<MethodAndSlotDef> {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
let wrapper_ident = syn::Ident::new("__pymethod___new____", Span::call_site());
let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?;
// Use just the text_signature_call_signature() because the class' Python name
@ -352,7 +355,6 @@ pub fn impl_py_method_def_new(
|| quote!(::std::option::Option::None),
|text_signature| quote!(::std::option::Option::Some(#text_signature)),
);
let deprecations = &spec.deprecations;
let slot_def = quote! {
#pyo3_path::ffi::PyType_Slot {
slot: #pyo3_path::ffi::Py_tp_new,
@ -361,11 +363,9 @@ pub fn impl_py_method_def_new(
subtype: *mut #pyo3_path::ffi::PyTypeObject,
args: *mut #pyo3_path::ffi::PyObject,
kwargs: *mut #pyo3_path::ffi::PyObject,
) -> *mut #pyo3_path::ffi::PyObject
{
#deprecations
) -> *mut #pyo3_path::ffi::PyObject {
use #pyo3_path::impl_::pyclass::*;
#[allow(unknown_lints, non_local_definitions)]
impl PyClassNewTextSignature<#cls> for PyClassImplCollector<#cls> {
#[inline]
fn new_text_signature(self) -> ::std::option::Option<&'static str> {
@ -391,7 +391,7 @@ pub fn impl_py_method_def_new(
}
fn impl_call_slot(cls: &syn::Type, mut spec: FnSpec<'_>, ctx: &Ctx) -> Result<MethodAndSlotDef> {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
// HACK: __call__ proto slot must always use varargs calling convention, so change the spec.
// Probably indicates there's a refactoring opportunity somewhere.
@ -431,12 +431,27 @@ fn impl_traverse_slot(
spec: &FnSpec<'_>,
ctx: &Ctx,
) -> syn::Result<MethodAndSlotDef> {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
if let (Some(py_arg), _) = split_off_python_arg(&spec.signature.arguments) {
return Err(syn::Error::new_spanned(py_arg.ty, "__traverse__ may not take `Python`. \
Usually, an implementation of `__traverse__` should do nothing but calls to `visit.call`. \
Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, \
i.e. `Python::with_gil` will panic."));
Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \
should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited \
inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic."));
}
// check that the receiver does not try to smuggle an (implicit) `Python` token into here
if let FnType::Fn(SelfType::TryFromBoundRef(span))
| FnType::Fn(SelfType::Receiver {
mutable: true,
span,
}) = spec.tp
{
bail_spanned! { span =>
"__traverse__ may not take a receiver other than `&self`. Usually, an implementation of \
`__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \
should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited \
inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic."
}
}
let rust_fn_ident = spec.name;
@ -467,11 +482,11 @@ fn impl_py_class_attribute(
spec: &FnSpec<'_>,
ctx: &Ctx,
) -> syn::Result<MethodAndMethodDef> {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
let (py_arg, args) = split_off_python_arg(&spec.signature.arguments);
ensure_spanned!(
args.is_empty(),
args[0].ty.span() => "#[classattr] can only have one argument (of type pyo3::Python)"
args[0].ty().span() => "#[classattr] can only have one argument (of type pyo3::Python)"
);
let name = &spec.name;
@ -482,7 +497,7 @@ fn impl_py_class_attribute(
};
let wrapper_ident = format_ident!("__pymethod_{}__", name);
let python_name = spec.null_terminated_python_name();
let python_name = spec.null_terminated_python_name(ctx);
let body = quotes::ok_wrap(fncall, ctx);
let associated_method = quote! {
@ -493,12 +508,14 @@ fn impl_py_class_attribute(
};
let method_def = quote! {
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
#pyo3_path::class::PyMethodDefType::ClassAttribute({
#pyo3_path::class::PyClassAttributeDef::new(
#python_name,
#pyo3_path::impl_::pymethods::PyClassAttributeFactory(#cls::#wrapper_ident)
#cls::#wrapper_ident
)
})
)
};
Ok(MethodAndMethodDef {
@ -521,7 +538,7 @@ fn impl_call_setter(
bail_spanned!(spec.name.span() => "setter function expected to have one argument");
} else if args.len() > 1 {
bail_spanned!(
args[1].ty.span() =>
args[1].ty().span() =>
"setter function can have at most two arguments ([pyo3::Python,] and value)"
);
}
@ -542,9 +559,9 @@ pub fn impl_py_setter_def(
property_type: PropertyType<'_>,
ctx: &Ctx,
) -> Result<MethodAndMethodDef> {
let Ctx { pyo3_path } = ctx;
let python_name = property_type.null_terminated_python_name()?;
let doc = property_type.doc();
let Ctx { pyo3_path, .. } = ctx;
let python_name = property_type.null_terminated_python_name(ctx)?;
let doc = property_type.doc(ctx);
let mut holders = Holders::new();
let setter_impl = match property_type {
PropertyType::Descriptor {
@ -590,15 +607,12 @@ pub fn impl_py_setter_def(
PropertyType::Function { spec, .. } => {
let (_, args) = split_off_python_arg(&spec.signature.arguments);
let value_arg = &args[0];
let (from_py_with, ident) = if let Some(from_py_with) =
&value_arg.attrs.from_py_with.as_ref().map(|f| &f.value)
{
let (from_py_with, ident) =
if let Some(from_py_with) = &value_arg.from_py_with().as_ref().map(|f| &f.value) {
let ident = syn::Ident::new("from_py_with", from_py_with.span());
(
quote_spanned! { from_py_with.span() =>
let e = #pyo3_path::impl_::deprecations::GilRefs::new();
let #ident = #pyo3_path::impl_::deprecations::inspect_fn(#from_py_with, &e);
e.from_py_with_arg();
let #ident = #from_py_with;
},
ident,
)
@ -606,21 +620,23 @@ pub fn impl_py_setter_def(
(quote!(), syn::Ident::new("dummy", Span::call_site()))
};
let extract = impl_arg_param(
&args[0],
let arg = if let FnArg::Regular(arg) = &value_arg {
arg
} else {
bail_spanned!(value_arg.name().span() => "The #[setter] value argument can't be *args, **kwargs or `cancel_handle`.");
};
let extract = impl_regular_arg_param(
arg,
ident,
quote!(::std::option::Option::Some(_value.into())),
&mut holders,
ctx,
)
.map(|tokens| {
check_arg_for_gil_refs(
tokens,
holders.push_gil_refs_checker(value_arg.ty.span()),
ctx,
)
})?;
);
let deprecation = deprecate_trailing_option_default(spec);
quote! {
#deprecation
#from_py_with
let _val = #extract;
}
@ -634,12 +650,8 @@ pub fn impl_py_setter_def(
.unwrap_or_default();
let holder = holders.push_holder(span);
let gil_refs_checker = holders.push_gil_refs_checker(span);
quote! {
let _val = #pyo3_path::impl_::deprecations::inspect_type(
#pyo3_path::impl_::extract_argument::extract_argument(_value.into(), &mut #holder, #name)?,
&#gil_refs_checker
);
let _val = #pyo3_path::impl_::extract_argument::extract_argument(_value.into(), &mut #holder, #name)?;
}
}
};
@ -656,7 +668,6 @@ pub fn impl_py_setter_def(
}
let init_holders = holders.init_holders(ctx);
let check_gil_refs = holders.check_gil_refs();
let associated_method = quote! {
#cfg_attrs
unsafe fn #wrapper_ident(
@ -672,20 +683,21 @@ pub fn impl_py_setter_def(
#init_holders
#extract
let result = #setter_impl;
#check_gil_refs
#pyo3_path::callback::convert(py, result)
}
};
let method_def = quote! {
#cfg_attrs
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
#pyo3_path::class::PyMethodDefType::Setter(
#pyo3_path::class::PySetterDef::new(
#python_name,
#pyo3_path::impl_::pymethods::PySetter(#cls::#wrapper_ident),
#cls::#wrapper_ident,
#doc
)
)
)
};
Ok(MethodAndMethodDef {
@ -705,7 +717,7 @@ fn impl_call_getter(
let slf = self_type.receiver(cls, ExtractErrorMode::Raise, holders, ctx);
ensure_spanned!(
args.is_empty(),
args[0].ty.span() => "getter function can only have one argument (of type pyo3::Python)"
args[0].ty().span() => "getter function can only have one argument (of type pyo3::Python)"
);
let name = &spec.name;
@ -724,64 +736,9 @@ pub fn impl_py_getter_def(
property_type: PropertyType<'_>,
ctx: &Ctx,
) -> Result<MethodAndMethodDef> {
let Ctx { pyo3_path } = ctx;
let python_name = property_type.null_terminated_python_name()?;
let doc = property_type.doc();
let mut holders = Holders::new();
let body = match property_type {
PropertyType::Descriptor {
field_index, field, ..
} => {
let slf = SelfType::Receiver {
mutable: false,
span: Span::call_site(),
}
.receiver(cls, ExtractErrorMode::Raise, &mut holders, ctx);
let field_token = if let Some(ident) = &field.ident {
// named struct field
ident.to_token_stream()
} else {
// tuple struct field
syn::Index::from(field_index).to_token_stream()
};
quotes::map_result_into_ptr(
quotes::ok_wrap(
quote! {
::std::clone::Clone::clone(&(#slf.#field_token))
},
ctx,
),
ctx,
)
}
// Forward to `IntoPyCallbackOutput`, to handle `#[getter]`s returning results.
PropertyType::Function {
spec, self_type, ..
} => {
let call = impl_call_getter(cls, spec, self_type, &mut holders, ctx)?;
quote! {
#pyo3_path::callback::convert(py, #call)
}
}
};
let wrapper_ident = match property_type {
PropertyType::Descriptor {
field: syn::Field {
ident: Some(ident), ..
},
..
} => {
format_ident!("__pymethod_get_{}__", ident)
}
PropertyType::Descriptor { field_index, .. } => {
format_ident!("__pymethod_get_field_{}__", field_index)
}
PropertyType::Function { spec, .. } => {
format_ident!("__pymethod_get_{}__", spec.name)
}
};
let Ctx { pyo3_path, .. } = ctx;
let python_name = property_type.null_terminated_python_name(ctx)?;
let doc = property_type.doc(ctx);
let mut cfg_attrs = TokenStream::new();
if let PropertyType::Descriptor { field, .. } = &property_type {
@ -794,8 +751,63 @@ pub fn impl_py_getter_def(
}
}
let mut holders = Holders::new();
match property_type {
PropertyType::Descriptor {
field_index, field, ..
} => {
let ty = &field.ty;
let field = if let Some(ident) = &field.ident {
ident.to_token_stream()
} else {
syn::Index::from(field_index).to_token_stream()
};
// TODO: on MSRV 1.77+, we can use `::std::mem::offset_of!` here, and it should
// make it possible for the `MaybeRuntimePyMethodDef` to be a `Static` variant.
let method_def = quote_spanned! {ty.span()=>
#cfg_attrs
{
#[allow(unused_imports)] // might not be used if all probes are positve
use #pyo3_path::impl_::pyclass::Probe;
struct Offset;
unsafe impl #pyo3_path::impl_::pyclass::OffsetCalculator<#cls, #ty> for Offset {
fn offset() -> usize {
#pyo3_path::impl_::pyclass::class_offset::<#cls>() +
#pyo3_path::impl_::pyclass::offset_of!(#cls, #field)
}
}
const GENERATOR: #pyo3_path::impl_::pyclass::PyClassGetterGenerator::<
#cls,
#ty,
Offset,
{ #pyo3_path::impl_::pyclass::IsPyT::<#ty>::VALUE },
{ #pyo3_path::impl_::pyclass::IsToPyObject::<#ty>::VALUE },
> = unsafe { #pyo3_path::impl_::pyclass::PyClassGetterGenerator::new() };
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Runtime(
|| GENERATOR.generate(#python_name, #doc)
)
}
};
Ok(MethodAndMethodDef {
associated_method: quote! {},
method_def,
})
}
// Forward to `IntoPyCallbackOutput`, to handle `#[getter]`s returning results.
PropertyType::Function {
spec, self_type, ..
} => {
let wrapper_ident = format_ident!("__pymethod_get_{}__", spec.name);
let call = impl_call_getter(cls, spec, self_type, &mut holders, ctx)?;
let body = quote! {
#pyo3_path::callback::convert(py, #call)
};
let init_holders = holders.init_holders(ctx);
let check_gil_refs = holders.check_gil_refs();
let associated_method = quote! {
#cfg_attrs
unsafe fn #wrapper_ident(
@ -804,32 +816,35 @@ pub fn impl_py_getter_def(
) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
#init_holders
let result = #body;
#check_gil_refs
result
}
};
let method_def = quote! {
#cfg_attrs
#pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
#pyo3_path::class::PyMethodDefType::Getter(
#pyo3_path::class::PyGetterDef::new(
#python_name,
#pyo3_path::impl_::pymethods::PyGetter(#cls::#wrapper_ident),
#cls::#wrapper_ident,
#doc
)
)
)
};
Ok(MethodAndMethodDef {
associated_method,
method_def,
})
}
}
}
/// Split an argument of pyo3::Python from the front of the arg list, if present
fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&FnArg<'_>>, &[FnArg<'_>]) {
fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&PyArg<'_>>, &[FnArg<'_>]) {
match args {
[py, args @ ..] if utils::is_python(py.ty) => (Some(py), args),
[FnArg::Py(py), args @ ..] => (Some(py), args),
args => (None, args),
}
}
@ -849,7 +864,7 @@ pub enum PropertyType<'a> {
}
impl PropertyType<'_> {
fn null_terminated_python_name(&self) -> Result<syn::LitStr> {
fn null_terminated_python_name(&self, ctx: &Ctx) -> Result<LitCStr> {
match self {
PropertyType::Descriptor {
field,
@ -864,23 +879,23 @@ impl PropertyType<'_> {
if let Some(rule) = renaming_rule {
name = utils::apply_renaming_rule(*rule, &name);
}
name.push('\0');
name
}
(None, None) => {
bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`");
}
};
Ok(syn::LitStr::new(&name, field.span()))
let name = CString::new(name).unwrap();
Ok(LitCStr::new(name, field.span(), ctx))
}
PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name()),
PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name(ctx)),
}
}
fn doc(&self) -> Cow<'_, PythonDoc> {
fn doc(&self, ctx: &Ctx) -> Cow<'_, PythonDoc> {
match self {
PropertyType::Descriptor { field, .. } => {
Cow::Owned(utils::get_doc(&field.attrs, None))
Cow::Owned(utils::get_doc(&field.attrs, None, ctx))
}
PropertyType::Function { doc, .. } => Cow::Borrowed(doc),
}
@ -889,10 +904,10 @@ impl PropertyType<'_> {
const __STR__: SlotDef = SlotDef::new("Py_tp_str", "reprfunc");
pub const __REPR__: SlotDef = SlotDef::new("Py_tp_repr", "reprfunc");
const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc")
pub const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc")
.ret_ty(Ty::PyHashT)
.return_conversion(TokenGenerator(
|Ctx { pyo3_path }: &Ctx| quote! { #pyo3_path::callback::HashCallbackOutput },
|Ctx { pyo3_path, .. }: &Ctx| quote! { #pyo3_path::callback::HashCallbackOutput },
));
pub const __RICHCMP__: SlotDef = SlotDef::new("Py_tp_richcompare", "richcmpfunc")
.extract_error_mode(ExtractErrorMode::NotImplemented)
@ -913,7 +928,7 @@ const __ANEXT__: SlotDef = SlotDef::new("Py_am_anext", "unaryfunc").return_speci
),
TokenGenerator(|_| quote! { async_iter_tag }),
);
const __LEN__: SlotDef = SlotDef::new("Py_mp_length", "lenfunc").ret_ty(Ty::PySsizeT);
pub const __LEN__: SlotDef = SlotDef::new("Py_mp_length", "lenfunc").ret_ty(Ty::PySsizeT);
const __CONTAINS__: SlotDef = SlotDef::new("Py_sq_contains", "objobjproc")
.arguments(&[Ty::Object])
.ret_ty(Ty::Int);
@ -923,7 +938,8 @@ const __INPLACE_CONCAT__: SlotDef =
SlotDef::new("Py_sq_concat", "binaryfunc").arguments(&[Ty::Object]);
const __INPLACE_REPEAT__: SlotDef =
SlotDef::new("Py_sq_repeat", "ssizeargfunc").arguments(&[Ty::PySsizeT]);
const __GETITEM__: SlotDef = SlotDef::new("Py_mp_subscript", "binaryfunc").arguments(&[Ty::Object]);
pub const __GETITEM__: SlotDef =
SlotDef::new("Py_mp_subscript", "binaryfunc").arguments(&[Ty::Object]);
const __POS__: SlotDef = SlotDef::new("Py_nb_positive", "unaryfunc");
const __NEG__: SlotDef = SlotDef::new("Py_nb_negative", "unaryfunc");
@ -1014,7 +1030,11 @@ enum Ty {
impl Ty {
fn ffi_type(self, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path } = ctx;
let Ctx {
pyo3_path,
output_span,
} = ctx;
let pyo3_path = pyo3_path.to_tokens_spanned(*output_span);
match self {
Ty::Object | Ty::MaybeNullObject => quote! { *mut #pyo3_path::ffi::PyObject },
Ty::NonNullObject => quote! { ::std::ptr::NonNull<#pyo3_path::ffi::PyObject> },
@ -1035,21 +1055,19 @@ impl Ty {
holders: &mut Holders,
ctx: &Ctx,
) -> TokenStream {
let Ctx { pyo3_path } = ctx;
let name_str = arg.name.unraw().to_string();
let Ctx { pyo3_path, .. } = ctx;
match self {
Ty::Object => extract_object(
extract_error_mode,
holders,
&name_str,
arg,
quote! { #ident },
arg.ty.span(),
ctx
),
Ty::MaybeNullObject => extract_object(
extract_error_mode,
holders,
&name_str,
arg,
quote! {
if #ident.is_null() {
#pyo3_path::ffi::Py_None()
@ -1057,23 +1075,20 @@ impl Ty {
#ident
}
},
arg.ty.span(),
ctx
),
Ty::NonNullObject => extract_object(
extract_error_mode,
holders,
&name_str,
arg,
quote! { #ident.as_ptr() },
arg.ty.span(),
ctx
),
Ty::IPowModulo => extract_object(
extract_error_mode,
holders,
&name_str,
arg,
quote! { #ident.as_ptr() },
arg.ty.span(),
ctx
),
Ty::CompareOp => extract_error_mode.handle_error(
@ -1084,7 +1099,7 @@ impl Ty {
ctx
),
Ty::PySsizeT => {
let ty = arg.ty;
let ty = arg.ty();
extract_error_mode.handle_error(
quote! {
::std::convert::TryInto::<#ty>::try_into(#ident).map_err(|e| #pyo3_path::exceptions::PyValueError::new_err(e.to_string()))
@ -1101,27 +1116,35 @@ impl Ty {
fn extract_object(
extract_error_mode: ExtractErrorMode,
holders: &mut Holders,
name: &str,
arg: &FnArg<'_>,
source_ptr: TokenStream,
span: Span,
ctx: &Ctx,
) -> TokenStream {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
let name = arg.name().unraw().to_string();
let extract =
if let Some(from_py_with) = arg.from_py_with().map(|from_py_with| &from_py_with.value) {
quote! {
#pyo3_path::impl_::extract_argument::from_py_with(
#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &#source_ptr).0,
#name,
#from_py_with as fn(_) -> _,
)
}
} else {
let holder = holders.push_holder(Span::call_site());
let gil_refs_checker = holders.push_gil_refs_checker(span);
let extracted = extract_error_mode.handle_error(
quote! {
#pyo3_path::impl_::extract_argument::extract_argument(
#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &#source_ptr).0,
&mut #holder,
#name
)
},
ctx,
);
quote! {
#pyo3_path::impl_::deprecations::inspect_type(#extracted, &#gil_refs_checker)
}
};
let extracted = extract_error_mode.handle_error(extract, ctx);
quote!(#extracted)
}
enum ReturnMode {
@ -1131,15 +1154,13 @@ enum ReturnMode {
}
impl ReturnMode {
fn return_call_output(&self, call: TokenStream, ctx: &Ctx, holders: &Holders) -> TokenStream {
let Ctx { pyo3_path } = ctx;
let check_gil_refs = holders.check_gil_refs();
fn return_call_output(&self, call: TokenStream, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path, .. } = ctx;
match self {
ReturnMode::Conversion(conversion) => {
let conversion = TokenGeneratorCtx(*conversion, ctx);
quote! {
let _result: #pyo3_path::PyResult<#conversion> = #pyo3_path::callback::convert(py, #call);
#check_gil_refs
#pyo3_path::callback::convert(py, _result)
}
}
@ -1149,14 +1170,12 @@ impl ReturnMode {
quote! {
let _result = #call;
use #pyo3_path::impl_::pymethods::{#traits};
#check_gil_refs
(&_result).#tag().convert(py, _result)
}
}
ReturnMode::ReturnSelf => quote! {
let _result: #pyo3_path::PyResult<()> = #pyo3_path::callback::convert(py, #call);
_result?;
#check_gil_refs
#pyo3_path::ffi::Py_XINCREF(_raw_slf);
::std::result::Result::Ok(_raw_slf)
},
@ -1235,7 +1254,7 @@ impl SlotDef {
method_name: &str,
ctx: &Ctx,
) -> Result<MethodAndSlotDef> {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
let SlotDef {
slot,
func_ty,
@ -1315,7 +1334,7 @@ fn generate_method_body(
return_mode: Option<&ReturnMode>,
ctx: &Ctx,
) -> Result<TokenStream> {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
let self_arg = spec
.tp
.self_arg(Some(cls), extract_error_mode, holders, ctx);
@ -1323,12 +1342,10 @@ fn generate_method_body(
let args = extract_proto_arguments(spec, arguments, extract_error_mode, holders, ctx)?;
let call = quote! { #cls::#rust_name(#self_arg #(#args),*) };
Ok(if let Some(return_mode) = return_mode {
return_mode.return_call_output(call, ctx, holders)
return_mode.return_call_output(call, ctx)
} else {
let check_gil_refs = holders.check_gil_refs();
quote! {
let result = #call;
#check_gil_refs;
#pyo3_path::callback::convert(py, result)
}
})
@ -1367,7 +1384,7 @@ impl SlotFragmentDef {
spec: &FnSpec<'_>,
ctx: &Ctx,
) -> Result<TokenStream> {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
let SlotFragmentDef {
fragment,
arguments,
@ -1507,12 +1524,12 @@ fn extract_proto_arguments(
let mut non_python_args = 0;
for arg in &spec.signature.arguments {
if arg.py {
if let FnArg::Py(..) = arg {
args.push(quote! { py });
} else {
let ident = syn::Ident::new(&format!("arg{}", non_python_args), Span::call_site());
let conversions = proto_args.get(non_python_args)
.ok_or_else(|| err_spanned!(arg.ty.span() => format!("Expected at most {} non-python arguments", proto_args.len())))?
.ok_or_else(|| err_spanned!(arg.ty().span() => format!("Expected at most {} non-python arguments", proto_args.len())))?
.extract(&ident, arg, extract_error_mode, holders, ctx);
non_python_args += 1;
args.push(conversions);

View File

@ -0,0 +1,3 @@
use pyo3_build_config::PythonVersion;
pub const PY_3_9: PythonVersion = PythonVersion { major: 3, minor: 9 };

View File

@ -1,23 +1,31 @@
use crate::utils::Ctx;
use proc_macro2::TokenStream;
use quote::quote;
use quote::{quote, quote_spanned};
pub(crate) fn some_wrap(obj: TokenStream, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
quote! {
#pyo3_path::impl_::wrap::SomeWrap::wrap(#obj)
}
}
pub(crate) fn ok_wrap(obj: TokenStream, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path } = ctx;
quote! {
let Ctx {
pyo3_path,
output_span,
} = ctx;
let pyo3_path = pyo3_path.to_tokens_spanned(*output_span);
quote_spanned! {*output_span=>
#pyo3_path::impl_::wrap::OkWrap::wrap(#obj)
.map_err(::core::convert::Into::<#pyo3_path::PyErr>::into)
}
}
pub(crate) fn map_result_into_ptr(result: TokenStream, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path } = ctx;
quote! { #pyo3_path::impl_::wrap::map_result_into_ptr(py, #result) }
let Ctx {
pyo3_path,
output_span,
} = ctx;
let pyo3_path = pyo3_path.to_tokens_spanned(*output_span);
quote_spanned! {*output_span=> #pyo3_path::impl_::wrap::map_result_into_ptr(py, #result) }
}

Some files were not shown because too many files have changed in this diff Show More