Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Complete abi3 support #1152

Merged
merged 65 commits into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
4004620
Proof of concept of using PEP384s PyType_Spec
alex Sep 1, 2020
62a175e
Merge pull request #1132 from alex/abi3-class-creation
kngwyu Sep 5, 2020
d2a10b6
Introduce all-apis feature to support abi3
kngwyu Sep 5, 2020
c2f10e2
Restructure protcol-table initialization
kngwyu Sep 5, 2020
1941f4d
Rename all-apis with unstable-api
kngwyu Sep 6, 2020
e0f75f8
Fix missing PyGetSetDef_INIT and Adress clippy warnings
kngwyu Sep 6, 2020
4cd6d4c
Fixed a few compilation errors on the abi3 branch
alex Sep 6, 2020
3b61df2
Merge pull request #1161 from alex/abi3-fix-errors
kngwyu Sep 7, 2020
80e2497
Complete the process of disabling buffers with Py_LIMITED_API
alex Sep 7, 2020
71a7b1a
Properly mark a funtion as limited API only
alex Sep 7, 2020
4325a59
Merge pull request #1164 from alex/abi3-no-free-func
kngwyu Sep 8, 2020
e8936be
Merge pull request #1162 from alex/disable-buffer-more
kngwyu Sep 8, 2020
0709a02
Fill tp_dict on types in an abi3-friendly way
alex Sep 8, 2020
679326e
Merge pull request #1165 from alex/abi3-fill-dict
kngwyu Sep 8, 2020
117f60b
Make PyType::name abi3 compatible
alex Sep 8, 2020
a009c23
Merge pull request #1166 from alex/abi3-name
kngwyu Sep 9, 2020
d6c9435
Implement set iterators in terms of limited API
alex Sep 8, 2020
0bc2393
Merge pull request #1167 from alex/abi3-sets
davidhewitt Sep 9, 2020
7a4c5e2
Merge branch 'master' into abi3
kngwyu Sep 9, 2020
5bfb467
Merge branch 'master' into abi3-merge-master
alex Sep 10, 2020
4d5c208
fixes
alex Sep 10, 2020
3cb0b11
Update src/err/mod.rs
alex Sep 12, 2020
afc2d10
Merge pull request #1172 from alex/abi3-merge-master
kngwyu Sep 13, 2020
d0c2ebf
Remove finalizer code that was never reachable and switch field access
alex Sep 13, 2020
1b2d267
Make unicode handling abi3 friendly
alex Sep 15, 2020
517af8c
Merge pull request #1183 from alex/abi3-tp-finalizer
kngwyu Sep 15, 2020
2ec1c3b
Merge pull request #1187 from alex/abi3-to-str
kngwyu Sep 15, 2020
870914d
Make check warning clean in limited API mode
alex Sep 15, 2020
a2dc4c1
Merge pull request #1188 from alex/abi3-warnings
kngwyu Sep 16, 2020
ba10560
Get all the tests building, everythign except doctests passes!
alex Sep 15, 2020
c87a59c
Merge pull request #1189 from alex/abi3-tests-compile
kngwyu Sep 18, 2020
c07e1aa
Use abi3 feature, instead of unstable-api
kngwyu Sep 19, 2020
2a85c17
Run abi3 tests in CI
alex Sep 19, 2020
7644d67
Inhibit subclassing native types with ABI3 set
kngwyu Sep 19, 2020
9d85591
Hack __text_signature__ back to working with abi3
alex Sep 19, 2020
4862f56
Merge pull request #1202 from alex/patch-1
kngwyu Sep 19, 2020
869a5e2
Fix an abi3 ui test for the latest Rustc
kngwyu Sep 20, 2020
1985578
Don't compile extends=PyDict test in class.md with abi3
kngwyu Sep 20, 2020
e33e58f
Merge pull request #1201 from alex/abi3-text-signature
kngwyu Sep 20, 2020
d8c8c17
Link python3.lib instead of python3x.lib on Windows in abi3 mode
alex Sep 23, 2020
e615ce8
Start documenting abi3 support
alex Sep 20, 2020
c22dd6c
Remove symbols not available in abi3
alex Sep 23, 2020
0fde737
Merge pull request #1207 from alex/abi3-link-python3
kngwyu Sep 28, 2020
20a93ed
Merge pull request #1203 from alex/abi3-docs
davidhewitt Oct 9, 2020
140790b
Merge branch 'master' into abi3-merge-master
alex Oct 10, 2020
398369f
Fixed warning
alex Oct 10, 2020
d42dbda
Merge pull request #1220 from alex/abi3-merge-master
kngwyu Oct 10, 2020
877667a
Improved documentation
alex Oct 11, 2020
137196d
Merge pull request #1227 from alex/abi3-improvements
davidhewitt Oct 11, 2020
aabad7c
Assorted updates to the abi3 branch from review
alex Oct 11, 2020
0665c02
Merge pull request #1230 from alex/abi3-final
davidhewitt Oct 12, 2020
9e34835
Merge branch 'master' into abi3-merge-master
alex Oct 12, 2020
5060379
Fix changelog
alex Oct 12, 2020
2923b4d
Fix for MSRV
alex Oct 12, 2020
4298435
Merge pull request #1237 from alex/abi3-merge-master
davidhewitt Oct 12, 2020
ba6f0ec
Merge branch 'master' into abi3-merge-master
alex Oct 18, 2020
265db33
Fixes for PyIterator
alex Oct 18, 2020
781bb9f
Merge branch 'master' into abi3-merge-master
alex Oct 18, 2020
90a825d
Merge branch 'master' into abi3-merge-master
alex Oct 19, 2020
f74b649
Merge pull request #1245 from alex/abi3-merge-master
davidhewitt Oct 19, 2020
eb8ff15
Renew PyProtoMethods for new ABI3-based type construction
kngwyu Oct 17, 2020
6627658
Renew proc-macros for new `#[pyproto]` backend
kngwyu Oct 19, 2020
16ad3bf
Use TypedSlot as internal representation of ffi::PyType_Slot
kngwyu Oct 26, 2020
95bec25
Merge pull request #1254 from PyO3/abi3-new-proto
kngwyu Oct 27, 2020
eb0e6f6
Note the minimum required version of maturin supporting abi3
kngwyu Oct 27, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ jobs:
- if: matrix.python-version != 'pypy3'
name: Test
run: cargo test --features "num-bigint num-complex" --target ${{ matrix.platform.rust-target }}
# Run tests again, but in abi3 mode
- if: matrix.python-version != 'pypy3'
name: Test (abi3)
run: cargo test --no-default-features --features "abi3,macros" --target ${{ matrix.platform.rust-target }}

- name: Test proc-macro code
run: cargo test --manifest-path=pyo3-derive-backend/Cargo.toml --target ${{ matrix.platform.rust-target }}
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Drop support for Python 3.5 (as it is now end-of-life). [#1250](https://github.com/PyO3/pyo3/pull/1250)

### Added
- Add support for building for CPython limited API. This required a few minor changes to runtime behaviour of of pyo3 `#[pyclass]` types. See the migration guide for full details. [#1152](https://github.com/PyO3/pyo3/pull/1152)
- Add argument names to `TypeError` messages generated by pymethod wrappers. [#1212](https://github.com/PyO3/pyo3/pull/1212)

### Changed
- Change return type `PyType::name()` from `Cow<str>` to `PyResult<&str>`. [#1152](https://github.com/PyO3/pyo3/pull/1152)
- `#[pyclass(subclass)]` is now required for subclassing from Rust (was previously just required for subclassing from Python). [#1152](https://github.com/PyO3/pyo3/pull/1152)
- Change `PyIterator` to be consistent with other native types: it is now used as `&PyIterator` instead of `PyIterator<'a>`. [#1176](https://github.com/PyO3/pyo3/pull/1176)
- Change formatting of `PyDowncastError` messages to be closer to Python's builtin error messages. [#1212](https://github.com/PyO3/pyo3/pull/1212)

Expand Down
9 changes: 4 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ rustversion = "1.0"
[features]
default = ["macros"]
macros = ["ctor", "indoc", "inventory", "paste", "pyo3cls", "unindent"]
# Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for
# more.
abi3 = []

# Optimizes PyObject to Vec conversion and so on.
nightly = []

Expand All @@ -43,11 +47,6 @@ nightly = []
# so that the module can also be used with statically linked python interpreters.
extension-module = []

# The stable cpython abi as defined in PEP 384. Currently broken with
# many compilation errors. Pull Requests working towards fixing that
# are welcome.
# abi3 = []

[workspace]
members = [
"pyo3cls",
Expand Down
5 changes: 5 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,11 @@ fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {

fn get_library_link_name(version: &PythonVersion, ld_version: &str) -> String {
if cfg!(target_os = "windows") {
// Mirrors the behavior in CPython's `PC/pyconfig.h`.
if env::var_os("CARGO_FEATURE_ABI3").is_some() {
return "python3".to_string();
}

let minor_or_empty_string = match version.minor {
Some(minor) => format!("{}", minor),
None => String::new(),
Expand Down
4 changes: 3 additions & 1 deletion examples/rustapi_module/tests/test_datetime.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import datetime as pdt
import platform
import struct
import re
import sys

import pytest
Expand Down Expand Up @@ -310,4 +311,5 @@ def test_tz_class_introspection():
tzi = rdt.TzClass()

assert tzi.__class__ == rdt.TzClass
assert repr(tzi).startswith("<TzClass object at")
# PyPy generates <importlib.bootstrap.TzClass ...> for some reason.
assert re.match(r"^<[\w\.]*TzClass object at", repr(tzi))
27 changes: 26 additions & 1 deletion guide/src/building_and_distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,27 @@ On Linux/macOS you might have to change `LD_LIBRARY_PATH` to include libpython,

## Distribution

There are two ways to distribute your module as a Python package: The old, [setuptools-rust](https://github.com/PyO3/setuptools-rust), and the new, [maturin](https://github.com/pyo3/maturin). setuptools-rust needs several configuration files (`setup.py`, `MANIFEST.in`, `build-wheels.sh`, etc.). maturin doesn't need any configuration files, however it does not support some functionality of setuptools such as package data ([pyo3/maturin#258](https://github.com/PyO3/maturin/issues/258)) and requires a rigid project structure, while setuptools-rust allows (and sometimes requires) configuration with python code.
There are two ways to distribute your module as a Python package: The old, [setuptools-rust], and the new, [maturin]. setuptools-rust needs several configuration files (`setup.py`, `MANIFEST.in`, `build-wheels.sh`, etc.). maturin doesn't need any configuration files, however it does not support some functionality of setuptools such as package data ([pyo3/maturin#258](https://github.com/PyO3/maturin/issues/258)) and requires a rigid project structure, while setuptools-rust allows (and sometimes requires) configuration with python code.

## `Py_LIMITED_API`/`abi3`

By default, Python extension modules can only be used with the same Python version they were compiled against -- if you build an extension module with Python 3.5, you can't import it using Python 3.8. [PEP 384](https://www.python.org/dev/peps/pep-0384/) introduced the idea of the limited Python API, which would have a stable ABI enabling extension modules built with it to be used against multiple Python versions. This is also known as `abi3`.

Note that [maturin] >= 0.9.0 or [setuptools-rust] >= 0.12.0 is going to support `abi3` wheels.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@konstin
Is it OK to say this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

See the [corresponding](https://github.com/PyO3/maturin/pull/353) [PRs](https://github.com/PyO3/setuptools-rust/pull/82) for more.

There are three steps involved in making use of `abi3` when building Python packages as wheels:

1. Enable the `abi3` feature in `pyo3`. This ensures `pyo3` only calls Python C-API functions which are part of the stable API, and on Windows also ensures that the project links against the correct shared object (no special behavior is required on other platforms):

```toml
[dependencies]
pyo3 = { version = "...", features = ["abi3"]}
```

2. Ensure that the built shared objects are correctly marked as `abi3`. This is accomplished by telling your build system that you're using the limited API.

3. Ensure that the `.whl` is correctly marked as `abi3`. For projects using `setuptools`, this is accomplished by passing `--py-limited-api=cp3x` (where `x` is the minimum Python version supported by the wheel, e.g. `--py-limited-api=cp35` for Python 3.5) to `setup.py bdist_wheel`.

## Cross Compiling

Expand Down Expand Up @@ -83,3 +103,8 @@ cargo build --target x86_64-pc-windows-gnu
## Bazel

For an example of how to build python extensions using Bazel, see https://github.com/TheButlah/rules_pyo3


[maturin]: https://github.com/PyO3/maturin
[setuptools-rust]: https://github.com/PyO3/setuptools-rust

29 changes: 21 additions & 8 deletions guide/src/class.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ or by `self_.into_super()` as `PyRef<Self::BaseClass>`.
```rust
# use pyo3::prelude::*;

#[pyclass]
#[pyclass(subclass)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a breaking change that needs to go in the CHANGELOG, and possibly other guide documentation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree.

struct BaseClass {
val1: usize,
}
Expand All @@ -222,7 +222,7 @@ impl BaseClass {
}
}

#[pyclass(extends=BaseClass)]
#[pyclass(extends=BaseClass, subclass)]
struct SubClass {
val2: usize,
}
Expand Down Expand Up @@ -266,12 +266,14 @@ impl SubSubClass {
```

You can also inherit native types such as `PyDict`, if they implement
[`PySizedLayout`](https://docs.rs/pyo3/latest/pyo3/type_object/trait.PySizedLayout.html).
[`PySizedLayout`](https://docs.rs/pyo3/latest/pyo3/type_object/trait.PySizedLayout.html). However, this is not supported when building for the Python limited API (aka the `abi3` feature of PyO3).

However, because of some technical problems, we don't currently provide safe upcasting methods for types
that inherit native types. Even in such cases, you can unsafely get a base class by raw pointer conversion.

```rust
# #[cfg(Py_LIMITED_API)] fn main() {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What part of this example is not supported by the limited api?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subclassing a builtin type (PyDict)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, of course. It could be worth adding a comment in this example which says this only works with the unlimited API. (Also the paragraph above starting on line 268 could also benefit from a similar statement saying inheriting native types is not possible with the limited api.)

# #[cfg(not(Py_LIMITED_API))] fn main() {
# use pyo3::prelude::*;
use pyo3::types::PyDict;
use pyo3::{AsPyPointer, PyNativeType};
Expand Down Expand Up @@ -300,6 +302,7 @@ impl DictWithCounter {
# let py = gil.python();
# let cnt = pyo3::PyCell::new(py, DictWithCounter::new()).unwrap();
# pyo3::py_run!(py, cnt, "cnt.set('abc', 10); assert cnt['abc'] == 10")
# }
```

If `SubClass` does not provide a baseclass initialization, the compilation fails.
Expand Down Expand Up @@ -769,13 +772,23 @@ impl pyo3::class::methods::HasMethodsInventory for MyClass {
}
pyo3::inventory::collect!(Pyo3MethodsInventoryForMyClass);

impl pyo3::class::proto_methods::HasProtoRegistry for MyClass {
fn registry() -> &'static pyo3::class::proto_methods::PyProtoRegistry {
static REGISTRY: pyo3::class::proto_methods::PyProtoRegistry
= pyo3::class::proto_methods::PyProtoRegistry::new();
&REGISTRY

pub struct Pyo3ProtoInventoryForMyClass {
def: pyo3::class::proto_methods::PyProtoMethodDef,
}
impl pyo3::class::proto_methods::PyProtoInventory for Pyo3ProtoInventoryForMyClass {
fn new(def: pyo3::class::proto_methods::PyProtoMethodDef) -> Self {
Self { def }
}
fn get(&'static self) -> &'static pyo3::class::proto_methods::PyProtoMethodDef {
&self.def
}
}
impl pyo3::class::proto_methods::HasProtoInventory for MyClass {
type ProtoMethods = Pyo3ProtoInventoryForMyClass;
}
pyo3::inventory::collect!(Pyo3ProtoInventoryForMyClass);


impl pyo3::pyclass::PyClassSend for MyClass {
type ThreadChecker = pyo3::pyclass::ThreadCheckerStub<MyClass>;
Expand Down
12 changes: 12 additions & 0 deletions guide/src/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
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.12.* to 0.13

### Runtime changes to support the CPython limited API

In PyO3 `0.13` support was added for compiling against the CPython limited API. This had a number of implications for _all_ PyO3 users, described here.

The largest of these is that all types created from PyO3 are what CPython calls "heap" types. The specific implications of this are:

- If you wish to subclass one of these types _from Rust_ you must mark it `#[pyclass(subclass)]`, as you would if you wished to allow subclassing it from Python code.
- Type objects are now mutable - Python code can set attributes on them.
- `__module__` on types without `#[pyclass(module="mymodule")]` no longer returns `builtins`, it now raises `AttributeError`.

## from 0.11.* to 0.12

### `PyErr` has been reworked
Expand Down
8 changes: 4 additions & 4 deletions guide/src/trait_bounds.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,8 +408,8 @@ impl Model for UserModel {
.call_method("get_results", (), None)
.unwrap();

if py_result.get_type().name() != "list" {
panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name());
if py_result.get_type().name().unwrap() != "list" {
panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name().unwrap());
}
py_result.extract()
})
Expand Down Expand Up @@ -536,8 +536,8 @@ impl Model for UserModel {
.call_method("get_results", (), None)
.unwrap();

if py_result.get_type().name() != "list" {
panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name());
if py_result.get_type().name().unwrap() != "list" {
panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name().unwrap());
}
py_result.extract()
})
Expand Down
Loading