Add numeric and object protocol examples

This commit is contained in:
mejrs 2022-02-23 10:08:42 +01:00
parent 463543ad58
commit be84e0fbb9
232 changed files with 7128 additions and 3712 deletions

View file

@ -5,11 +5,9 @@ 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
Be aware the CI pipeline will check your pull request for the following:
- Rust tests (Just `cargo test` or `make test` if you need to test examples)
- Rust lints (`make clippy`)
- Rust formatting (`cargo fmt`)
- Python formatting (`black . --check`. You can install black with `pip install black`)
- Compatibility with all supported Python versions for all examples. This uses `tox`; you can do run it using `cargo xtask test-py`.
You can run a similar set of checks as the CI pipeline using `make test`.
Be aware the CI pipeline will check your pull request for the following. This is done using `nox` (you can install with `pip install nox`):
- Rust tests (`cargo test` or `nox -s test-rust`)
- Examples (`nox -s test-py`)
- Rust lints (`nox -s clippy`)
- Rust formatting (`nox -s fmt-rust`)
- Python formatting (`nox -s fmt-py`)

View file

@ -83,10 +83,8 @@ jobs:
- name: Run benchmarks
run: |
cd pytests/pyo3-benchmarks
pip install -r requirements-dev.txt
pip install .
pytest --benchmark-json ../../output.json --benchmark-enable
pip install nox
nox -f pytests/noxfile.py -s bench -- --benchmark-json $(pwd)/output.json
- name: Store benchmark result
uses: rhysd/github-action-benchmark@v1
with:

View file

@ -15,27 +15,29 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- run: pip install black==21.12b0
- run: pip install nox
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: rustfmt
- name: Check python formatting (black)
run: make fmt_py
run: nox -s fmt-py
- name: Check rust formatting (rustfmt)
run: make fmt_rust
run: nox -s fmt-rust
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- run: pip install nox
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: clippy
- run: make clippy
- run: nox -s clippy
check-target:
needs: [fmt]
@ -70,13 +72,13 @@ jobs:
build:
needs: [fmt] # don't wait for clippy as fails rarely and takes longer
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} ${{ matrix.msrv }}
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} rust-${{ matrix.rust }}
runs-on: ${{ matrix.platform.os }}
strategy:
fail-fast: false # If one platform fails, allow the rest to keep testing.
matrix:
rust: [stable]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev", "pypy-3.7", "pypy-3.8"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev", "pypy-3.7-v7.3.7", "pypy-3.8"]
platform:
[
{
@ -102,7 +104,7 @@ jobs:
]
exclude:
# PyPy doesn't release 32-bit Windows builds any more
- python-version: pypy-3.7
- python-version: pypy-3.7-v7.3.7
platform: { os: "windows-latest", python-architecture: "x86" }
- python-version: pypy-3.8
platform: { os: "windows-latest", python-architecture: "x86" }
@ -118,6 +120,16 @@ jobs:
}
msrv: "MSRV"
# Test the `nightly` feature
- rust: nightly
python-version: "3.10"
platform:
{
os: "ubuntu-latest",
python-architecture: "x64",
rust-target: "x86_64-unknown-linux-gnu",
}
extra_features: "nightly"
steps:
- uses: actions/checkout@v2
@ -153,7 +165,7 @@ jobs:
cargo update -p hashbrown:0.11.2 --precise 0.9.1
- name: Build docs
run: cargo doc --no-deps --no-default-features --features full
run: cargo doc --no-deps --no-default-features --features "full ${{ matrix.extra_features }}"
- name: Build (no features)
run: cargo build --lib --tests --no-default-features
@ -176,26 +188,26 @@ jobs:
cargo test --no-default-features
- name: Build (all additive features)
run: cargo build --lib --tests --no-default-features --features full
run: cargo build --lib --tests --no-default-features --features "full ${{ matrix.extra_features }}"
- if: ${{ startsWith(matrix.python-version, 'pypy') }}
name: Build PyPy (abi3-py37)
run: cargo build --lib --tests --no-default-features --features "abi3-py37 full"
run: cargo build --lib --tests --no-default-features --features "abi3-py37 full ${{ matrix.extra_features }}"
# Run tests (except on PyPy, because no embedding API).
- if: ${{ !startsWith(matrix.python-version, 'pypy') }}
name: Test
run: cargo test --no-default-features --features full
run: cargo test --no-default-features --features "full ${{ matrix.extra_features }}"
# Run tests again, but in abi3 mode
- if: ${{ !startsWith(matrix.python-version, 'pypy') }}
name: Test (abi3)
run: cargo test --no-default-features --features "abi3 full"
run: cargo test --no-default-features --features "abi3 full ${{ matrix.extra_features }}"
# Run tests again, for abi3-py37 (the minimal Python version)
- if: ${{ (!startsWith(matrix.python-version, 'pypy')) && (matrix.python-version != '3.7') }}
name: Test (abi3-py37)
run: cargo test --no-default-features --features "abi3-py37 full"
run: cargo test --no-default-features --features "abi3-py37 full ${{ matrix.extra_features }}"
- name: Test proc-macro code
run: cargo test --manifest-path=pyo3-macros-backend/Cargo.toml
@ -259,7 +271,7 @@ jobs:
components: llvm-tools-preview
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- run: pip install -U pip nox
- run: python -m pip install -U pip nox
- run: cargo xtask coverage --output-lcov coverage.lcov
- uses: codecov/codecov-action@v2
with:

View file

@ -19,7 +19,7 @@ Since implementing `PyClass` requires lots of boilerplate, we have a proc-macro
To summarize, there are six main parts to the PyO3 codebase.
1. [Low-level bindings of Python/C API.](#1-low-level-bindings-of-python-capi)
- [`src/ffi`]
- [`pyo3-ffi`] and [`src/ffi`]
2. [Bindings to Python objects.](#2-bindings-to-python-objects)
- [`src/instance.rs`] and [`src/types`]
3. [`PyClass` and related functionalities.](#3-pyclass-and-related-functionalities)
@ -34,7 +34,7 @@ To summarize, there are six main parts to the PyO3 codebase.
## 1. Low-level bindings of Python/C API
[`src/ffi`] contains wrappers of [Python/C API].
[`pyo3-ffi`] contains wrappers of [Python/C API].
We aim to provide straight-forward Rust wrappers resembling the file structure of
[`cpython/Include`](https://github.com/python/cpython/tree/v3.9.2/Include).
@ -43,7 +43,7 @@ However, we still lack some APIs and are continuously updating the module to mat
the file contents upstream in CPython.
The tracking issue is [#1289](https://github.com/PyO3/pyo3/issues/1289), and contribution is welcome.
In the [`src/ffi`] module, there is lots of conditional compilation such as `#[cfg(Py_LIMITED_API)]`,
In the [`pyo3-ffi`] crate, there is lots of conditional compilation such as `#[cfg(Py_LIMITED_API)]`,
`#[cfg(Py_37)]`, and `#[cfg(PyPy)]`.
`Py_LIMITED_API` corresponds to `#define Py_LIMITED_API` macro in Python/C API.
With `Py_LIMITED_API`, we can build a Python-version-agnostic binary called an
@ -208,6 +208,7 @@ Some of the functionality of `pyo3-build-config`:
[`pyo3-macros`]: https://github.com/PyO3/pyo3/tree/main/pyo3-macros
[`pyo3-macros-backend`]: https://github.com/PyO3/pyo3/tree/main/pyo3-macros-backend
[`pyo3-build-config`]: https://github.com/PyO3/pyo3/tree/main/pyo3-build-config
[`pyo3-ffi`]: https://github.com/PyO3/pyo3/tree/main/pyo3-ffi
<!-- Directories -->

View file

@ -16,19 +16,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `pyo3-build-config` no longer enables the `resolve-config` feature by default. [#2008](https://github.com/PyO3/pyo3/pull/2008)
- Update `inventory` optional dependency to 0.2. [#2019](https://github.com/PyO3/pyo3/pull/2019)
- Drop `paste` dependency. [#2081](https://github.com/PyO3/pyo3/pull/2081)
- The bindings found `pyo3::ffi` are now a re-export of the new `pyo3-ffi` crate. [#2126](https://github.com/PyO3/pyo3/pull/2126)
### Added
- Add `Py::setattr` method. [#2009](https://github.com/PyO3/pyo3/pull/2009)
- Add `PyCapsule`, exposing the [Capsule API](https://docs.python.org/3/c-api/capsule.html#capsules). [#1980](https://github.com/PyO3/pyo3/pull/1980)
- All PyO3 proc-macros except the deprecated `#[pyproto]` now accept a supplemental attribute `#[pyo3(crate = "some::path")]` that specifies
where to find the `pyo3` crate, in case it has been renamed or is re-exported and not found at the crate root. [#2022](https://github.com/PyO3/pyo3/pull/2022)
- Expose `pyo3-build-config` APIs for cross-compiling and Python configuration discovery for use in other projects. [#1996](https://github.com/PyO3/pyo3/pull/1996)
- All PyO3 proc-macros except the deprecated `#[pyproto]` now accept a supplemental attribute `#[pyo3(crate = "some::path")]` that specifies where to find the `pyo3` crate, in case it has been renamed or is re-exported and not found at the crate root. [#2022](https://github.com/PyO3/pyo3/pull/2022)
- Enable `#[pyclass]` for fieldless (aka C-like) enums. [#2034](https://github.com/PyO3/pyo3/pull/2034)
- Add buffer magic methods `__getbuffer__` and `__releasebuffer__` to `#[pymethods]`. [#2067](https://github.com/PyO3/pyo3/pull/2067)
- Accept paths in `wrap_pyfunction` and `wrap_pymodule`. [#2081](https://github.com/PyO3/pyo3/pull/2081)
- Add check for correct number of arguments on magic methods. [#2083](https://github.com/PyO3/pyo3/pull/2083)
- `wrap_pyfunction!` can now wrap a `#[pyfunction]` which is implemented in a different Rust module or crate. [#2091](https://github.com/PyO3/pyo3/pull/2091)
- Add `PyAny::contains` method (`in` operator for `PyAny`). [#2115](https://github.com/PyO3/pyo3/pull/2115)
- Add `PyMapping::contains` method (`in` operator for `PyMapping`). [#2133](https://github.com/PyO3/pyo3/pull/2133)
- Add garbage collection magic methods `__traverse__` and `__clear__` to `#[pymethods]`. [#2159](https://github.com/PyO3/pyo3/pull/2159)
### Changed
@ -43,17 +46,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `PyErr::new_type` now takes an optional docstring and now returns `PyResult<Py<PyType>>` rather than a `ffi::PyTypeObject` pointer.
- The `create_exception!` macro can now take an optional docstring. This docstring, if supplied, is visible to users (with `.__doc__` and `help()`) and
accompanies your error type in your crate's documentation.
- `__getitem__`, `__setitem__` and `__delitem__` in `#[pymethods]` now implement both a Python mapping and sequence by default. [#2065](https://github.com/PyO3/pyo3/pull/2065)
- Improve performance and error messages for `#[derive(FromPyObject)]` for enums. [#2068](https://github.com/PyO3/pyo3/pull/2068)
- Reduce generated LLVM code size (to improve compile times) for:
- internal `handle_panic` helper [#2074](https://github.com/PyO3/pyo3/pull/2074)
- internal `handle_panic` helper [#2074](https://github.com/PyO3/pyo3/pull/2074) [#2158](https://github.com/PyO3/pyo3/pull/2158)
- `#[pyfunction]` and `#[pymethods]` argument extraction [#2075](https://github.com/PyO3/pyo3/pull/2075) [#2085](https://github.com/PyO3/pyo3/pull/2085)
- `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076) [#2081](https://github.com/PyO3/pyo3/pull/2081)
- `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076) [#2081](https://github.com/PyO3/pyo3/pull/2081) [#2157](https://github.com/PyO3/pyo3/pull/2157)
- `__ipow__` now supports modulo argument on Python 3.8+. [#2083](https://github.com/PyO3/pyo3/pull/2083)
- `pyo3-macros-backend` is now compiled with PyO3 cfgs to enable different magic method definitions based on version. [#2083](https://github.com/PyO3/pyo3/pull/2083)
- `_PyCFunctionFast` now correctly reflects the signature defined in the [Python docs](https://docs.python.org/3/c-api/structures.html#c._PyCFunctionFast). [#2126](https://github.com/PyO3/pyo3/pull/2126)
- `PyDateTimeAPI` and `PyDateTime_TimeZone_UTC` are are now unsafe functions instead of statics. [#2126](https://github.com/PyO3/pyo3/pull/2126)
- `PyDateTimeAPI` does not implicitly call `PyDateTime_IMPORT` anymore to reflect the original Python API more closely. Before the first call to `PyDateTime_IMPORT` a null pointer is returned. Therefore before calling any of the following FFI functions `PyDateTime_IMPORT` must be called to avoid undefined behaviour: [#2126](https://github.com/PyO3/pyo3/pull/2126)
- `PyDateTime_TimeZone_UTC`
- `PyDate_Check`
- `PyDate_CheckExact`
- `PyDateTime_Check`
- `PyDateTime_CheckExact`
- `PyTime_Check`
- `PyTime_CheckExact`
- `PyDelta_Check`
- `PyDelta_CheckExact`
- `PyTZInfo_Check`
- `PyTZInfo_CheckExact`
- `PyDateTime_FromTimestamp`
- `PyDate_FromTimestamp`
- Deprecate the `gc` option for `pyclass` (e.g. `#[pyclass(gc)]`). Just implement a `__traverse__` `#[pymethod]`. [#2159](https://github.com/PyO3/pyo3/pull/2159)
- The `ml_meth` field of `PyMethodDef` is now represented by the `PyMethodDefPointer` union [2166](https://github.com/PyO3/pyo3/pull/2166)
- The `PyTypeError` thrown when argument parsing failed now contains the nested causes [2177](https://github.com/PyO3/pyo3/pull/2178)
### Removed
- Remove all functionality deprecated in PyO3 0.14. [#2007](https://github.com/PyO3/pyo3/pull/2007)
- Remove `Default` impl for `PyMethodDef` [2166](https://github.com/PyO3/pyo3/pull/2166)
### Fixed
@ -64,6 +88,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Use the Rust function path for `wrap_pymodule!` of a `#[pymodule]` with a `#[pyo3(name = "..")]` attribute, not the Python name. [#2081](https://github.com/PyO3/pyo3/pull/2081)
- Fix panic in `#[pyfunction]` generated code when a required argument following an `Option` was not provided. [#2093](https://github.com/PyO3/pyo3/pull/2093)
- Fixed undefined behaviour caused by incorrect `ExactSizeIterator` implementations. [#2124](https://github.com/PyO3/pyo3/pull/2124)
- Add missing FFI definitions `_PyLong_NumBits` and `_PyLong_AsByteArray` on PyPy. [#2146](https://github.com/PyO3/pyo3/pull/2146)
- Fix memory leak in implementation of `AsPyPointer` for `Option<T>`. [#2160](https://github.com/PyO3/pyo3/pull/2160)
- Fix the signature of `_PyLong_NumBits` [#2161](https://github.com/PyO3/pyo3/pull/2161)
## [0.15.1] - 2021-11-19

View file

@ -19,6 +19,9 @@ cfg-if = "1.0"
libc = "0.2.62"
parking_lot = "0.11.0"
# ffi bindings to the python interpreter, split into a seperate crate so they can be used independently
pyo3-ffi = { path = "pyo3-ffi", version = "=0.15.1" }
# support crates for macros feature
pyo3-macros = { path = "pyo3-macros", version = "=0.15.1", optional = true }
indoc = { version = "1.0.3", optional = true }
@ -43,13 +46,14 @@ trybuild = "1.0.49"
rustversion = "1.0"
# 1.0.0 requires Rust 1.50
proptest = { version = "0.10.1", default-features = false, features = ["std"] }
send_wrapper = "0.5"
serde_json = "1.0.61"
[build-dependencies]
pyo3-build-config = { path = "pyo3-build-config", version = "0.15.1", features = ["resolve-config"] }
[features]
default = ["macros"]
default = ["macros", "pyproto"]
# Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc.
macros = ["pyo3-macros", "indoc", "unindent"]
@ -57,19 +61,22 @@ macros = ["pyo3-macros", "indoc", "unindent"]
# Enables multiple #[pymethods] per #[pyclass]
multiple-pymethods = ["inventory", "pyo3-macros/multiple-pymethods"]
# Enables deprecated #[pyproto] macro
pyproto = ["pyo3-macros/pyproto"]
# Use this feature when building an extension module.
# It tells the linker to keep the python symbols unresolved,
# so that the module can also be used with statically linked python interpreters.
extension-module = []
extension-module = ["pyo3-ffi/extension-module"]
# Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more.
abi3 = ["pyo3-build-config/abi3"]
abi3 = ["pyo3-build-config/abi3", "pyo3-ffi/abi3"]
# With abi3, we can manually set the minimum Python version.
abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37"]
abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38"]
abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39"]
abi3-py310 = ["abi3", "pyo3-build-config/abi3-py310"]
abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37", "pyo3-ffi/abi3-py37"]
abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38", "pyo3-ffi/abi3-py38"]
abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39", "pyo3-ffi/abi3-py39"]
abi3-py310 = ["abi3", "pyo3-build-config/abi3-py310", "pyo3-ffi/abi3-py310"]
# Changes `Python::with_gil` and `Python::acquire_gil` to automatically initialize the
# Python interpreter if needed.
@ -80,7 +87,7 @@ nightly = []
# Activates all additional features
# This is mostly intended for testing purposes - activating *all* of these isn't particularly useful.
full = ["macros", "multiple-pymethods", "num-bigint", "num-complex", "hashbrown", "serde", "indexmap", "eyre", "anyhow"]
full = ["macros", "pyproto", "multiple-pymethods", "num-bigint", "num-complex", "hashbrown", "serde", "indexmap", "eyre", "anyhow"]
[[bench]]
name = "bench_call"
@ -126,10 +133,11 @@ harness = false
[workspace]
members = [
"pyo3-ffi",
"pyo3-build-config",
"pyo3-macros",
"pyo3-macros-backend",
"pytests/pyo3-benchmarks",
"pytests/pyo3-pytests",
"pytests",
"examples",
"xtask"
]

View file

@ -9,7 +9,7 @@ If you want to become familiar with the codebase, see
## Getting started contributing
Please join in with any part of PyO3 which interests you. We use GitHub issues to record all bugs and ideas. Feel free to request an issue to be assigned to you if you want to work on it.
Please join in with any part of PyO3 which interests you. We use GitHub issues to record all bugs and ideas. Feel free to request an issue to be assigned to you if you want to work on it.
You can browse the API of the non-public parts of PyO3 [here](https://pyo3.rs/internal/doc/pyo3/index.html).
@ -47,7 +47,7 @@ There are some specific areas of focus where help is currently needed for the do
- Issues requesting documentation improvements are tracked with the [documentation](https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+label%3Adocumentation) label.
- Not all APIs had docs or examples when they were made. The goal is to have documentation on all PyO3 APIs ([#306](https://github.com/PyO3/pyo3/issues/306)). If you see an API lacking a doc, please write one and open a PR!
You can build the docs (including all features) with
You can build the docs (including all features) with
```cargo +nightly pyo3_doc_scrape```
#### Doctests
@ -115,7 +115,7 @@ First, there are Rust-based benchmarks located in the `benches` subdirectory. As
cargo +nightly bench
Second, there is a Python-based benchmark contained in the `pyo3-benchmarks` example. You can read more about it [here](examples/pyo3-benchmarks).
Second, there is a Python-based benchmark contained in the `pytests` subdirectory. You can read more about it [here](pytests).
## Sponsor this project

View file

@ -1,59 +0,0 @@
.PHONY: test test_py publish clippy lint fmt fmt_py fmt_rust
ALL_ADDITIVE_FEATURES = macros multiple-pymethods num-bigint num-complex hashbrown serde indexmap eyre anyhow
COVERAGE_PACKAGES = --package pyo3 --package pyo3-build-config --package pyo3-macros-backend --package pyo3-macros
list_all_additive_features:
@echo $(ALL_ADDITIVE_FEATURES)
test: lint test_py
cargo test
cargo test --features="abi3"
cargo test --features="$(ALL_ADDITIVE_FEATURES)"
cargo test --features="abi3 $(ALL_ADDITIVE_FEATURES)"
test_py:
@for example in examples/*/noxfile.py; do echo "-- Running nox for $$example --"; nox -f $$example/noxfile.py || exit 1; echo ""; done
@for package in pytests/*/noxfile.py; do echo "-- Running nox for $$package --"; nox -f $$package/noxfile.py || exit 1; echo ""; done
fmt_py:
black . --check
fmt_rust:
cargo fmt --all -- --check
for package in pytests/*/; do cargo fmt --manifest-path $$package/Cargo.toml -- --check || exit 1; done
fmt: fmt_rust fmt_py
@true
coverage:
# cargo llvm-cov clean --workspace
# cargo llvm-cov $(COVERAGE_PACKAGES) --no-report
# cargo llvm-cov $(COVERAGE_PACKAGES) --no-report --features abi3
# cargo llvm-cov $(COVERAGE_PACKAGES) --no-report --features $(ALL_ADDITIVE_FEATURES)
# cargo llvm-cov $(COVERAGE_PACKAGES) --no-report --features abi3 $(ALL_ADDITIVE_FEATURES)
bash -c "\
set -a\
source <(cargo llvm-cov show-env)\
make test_py\
"
cargo llvm-cov $(COVERAGE_PACKAGES) --no-run --summary-only
clippy:
cargo clippy --features="$(ALL_ADDITIVE_FEATURES)" --all-targets --workspace -- -Dwarnings
cargo clippy --features="abi3 $(ALL_ADDITIVE_FEATURES)" --all-targets --workspace -- -Dwarnings
for example in examples/*/; do cargo clippy --manifest-path $$example/Cargo.toml -- -Dwarnings || exit 1; done
for package in pytests/*/; do cargo clippy --manifest-path $$package/Cargo.toml -- -Dwarnings || exit 1; done
lint: fmt clippy
@true
publish: test
cargo publish --manifest-path pyo3-build-config/Cargo.toml
sleep 10
cargo publish --manifest-path pyo3-macros-backend/Cargo.toml
sleep 10 # wait for crates.io to update
cargo publish --manifest-path pyo3-macros/Cargo.toml
sleep 10 # wait for crates.io to update
cargo publish

View file

@ -56,6 +56,9 @@ version = "0.1.0"
edition = "2018"
[lib]
# The name of the native library. This is the name which will be used in Python to import the
# library (i.e. `import string_sum`). If you change this, you must also change the name of the
# `#[pymodule]` in `src/lib.rs`.
name = "string_sum"
# "cdylib" is necessary to produce a shared library for Python to import from.
#
@ -159,7 +162,7 @@ about this topic.
## Tools and libraries
- [maturin](https://github.com/PyO3/maturin) _Zero configuration build tool for Rust-made Python extensions_.
- [maturin](https://github.com/PyO3/maturin) _Build and publish crates with pyo3, rust-cpython or cffi bindings as well as rust binaries as python packages_
- [setuptools-rust](https://github.com/PyO3/setuptools-rust) _Setuptools plugin for Rust support_.
- [pyo3-built](https://github.com/PyO3/pyo3-built) _Simple macro to expose metadata obtained with the [`built`](https://crates.io/crates/built) crate as a [`PyDict`](https://docs.rs/pyo3/*/pyo3/types/struct.PyDict.html)_
- [rust-numpy](https://github.com/PyO3/rust-numpy) _Rust binding of NumPy C-API_

128
build.rs
View file

@ -1,48 +1,7 @@
use std::{env, process::Command};
use std::env;
use pyo3_build_config::{
bail, ensure,
pyo3_build_script_impl::{
cargo_env_var, env_var, errors::Result, resolve_interpreter_config, InterpreterConfig,
PythonVersion,
},
};
/// Minimum Python version PyO3 supports.
const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 6 };
fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> {
ensure!(
interpreter_config.version >= MINIMUM_SUPPORTED_VERSION,
"the configured Python interpreter version ({}) is lower than PyO3's minimum supported version ({})",
interpreter_config.version,
MINIMUM_SUPPORTED_VERSION,
);
Ok(())
}
fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result<()> {
if let Some(pointer_width) = interpreter_config.pointer_width {
// Try to check whether the target architecture matches the python library
let rust_target = match cargo_env_var("CARGO_CFG_TARGET_POINTER_WIDTH")
.unwrap()
.as_str()
{
"64" => 64,
"32" => 32,
x => bail!("unexpected Rust target pointer width: {}", x),
};
ensure!(
rust_target == pointer_width,
"your Rust target architecture ({}-bit) does not match your python interpreter ({}-bit)",
rust_target,
pointer_width
);
}
Ok(())
}
use pyo3_build_config::pyo3_build_script_impl::{cargo_env_var, errors::Result};
use pyo3_build_config::{bail, print_feature_cfgs, InterpreterConfig};
fn ensure_auto_initialize_ok(interpreter_config: &InterpreterConfig) -> Result<()> {
if cargo_env_var("CARGO_FEATURE_AUTO_INITIALIZE").is_some() {
@ -73,47 +32,6 @@ fn ensure_auto_initialize_ok(interpreter_config: &InterpreterConfig) -> Result<(
Ok(())
}
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()
}
fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> {
let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap();
let is_extension_module = cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some();
if target_os == "windows" || target_os == "android" || !is_extension_module {
// windows and android - always link
// other systems - only link if not extension module
println!(
"cargo:rustc-link-lib={link_model}{alias}{lib_name}",
link_model = if interpreter_config.shared {
""
} else {
"static="
},
alias = if target_os == "windows" {
"pythonXY:"
} else {
""
},
lib_name = interpreter_config.lib_name.as_ref().ok_or(
"attempted to link to Python shared library but config does not contain lib_name"
)?,
);
if let Some(lib_dir) = &interpreter_config.lib_dir {
println!("cargo:rustc-link-search=native={}", lib_dir);
}
}
Ok(())
}
/// Prepares the PyO3 crate for compilation.
///
/// This loads the config from pyo3-build-config and then makes some additional checks to improve UX
@ -122,50 +40,18 @@ fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> {
/// Emits the cargo configuration based on this config as well as a few checks of the Rust compiler
/// version to enable features which aren't supported on MSRV.
fn configure_pyo3() -> Result<()> {
let interpreter_config = resolve_interpreter_config()?;
if env_var("PYO3_PRINT_CONFIG").map_or(false, |os_str| os_str == "1") {
print_config_and_exit(&interpreter_config);
}
ensure_python_version(&interpreter_config)?;
ensure_target_pointer_width(&interpreter_config)?;
ensure_auto_initialize_ok(&interpreter_config)?;
if !interpreter_config.suppress_build_script_link_lines {
emit_link_config(&interpreter_config)?;
}
let interpreter_config = pyo3_build_config::get();
interpreter_config.emit_pyo3_cfgs();
let rustc_minor_version = rustc_minor_version().unwrap_or(0);
ensure_auto_initialize_ok(interpreter_config)?;
// Enable use of const generics on Rust 1.51 and greater
if rustc_minor_version >= 51 {
println!("cargo:rustc-cfg=min_const_generics");
}
// Enable use of std::ptr::addr_of! on Rust 1.51 and greater
if rustc_minor_version >= 51 {
println!("cargo:rustc-cfg=addr_of");
}
// Extra lines come last, to support last write wins.
for line in &interpreter_config.extra_build_script_lines {
println!("{}", line);
}
// Emit cfgs like `addr_of` and `min_const_generics`
print_feature_cfgs();
Ok(())
}
fn print_config_and_exit(config: &InterpreterConfig) {
println!("\n-- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile --");
config
.to_writer(&mut std::io::stdout())
.expect("failed to print config to stdout");
std::process::exit(101);
}
fn main() {
if let Err(e) = configure_pyo3() {
eprintln!("error: {}", e.report());

View file

@ -8,6 +8,8 @@
- [Python Functions](function.md)
- [Python Classes](class.md)
- [Class customizations](class/protocols.md)
- [Basic object customization](class/object.md)
- [Emulating numeric types](class/numeric.md)
- [Emulating callable objects](class/call.md)
- [Type Conversions](conversions.md)
- [Mapping of Rust types to Python types](conversions/tables.md)]

View file

@ -163,7 +163,7 @@ not work when compiling for `abi3`. These are:
- `#[pyo3(text_signature = "...")]` does not work on classes until Python 3.10 or greater.
- The `dict` and `weakref` options on classes are not supported until Python 3.9 or greater.
- The buffer API is not supported.
- The buffer API is not supported until Python 3.11 or greater.
- Optimizations which rely on knowledge of the exact Python version compiled against.
## Embedding Python in Rust

View file

@ -2,7 +2,7 @@
PyO3 exposes a group of attributes powered by Rust's proc macro system for defining Python classes as Rust structs.
The main attribute is `#[pyclass]`, which is placed upon a Rust `struct` to generate a Python type for it. A struct will usually also have *one* `#[pymethods]`-annotated `impl` block for the struct, which is used to define Python methods and constants for the generated Python type. (If the [`multiple-pymethods`] feature is enabled each `#[pyclass]` is allowed to have multiple `#[pymethods]` blocks.) Finally, there may be multiple `#[pyproto]` trait implementations for the struct, which are used to define certain python magic methods such as `__str__`.
The main attribute is `#[pyclass]`, which is placed upon a Rust `struct` or a fieldless `enum` (a.k.a. C-like enum) to generate a Python type for it. They will usually also have *one* `#[pymethods]`-annotated `impl` block for the struct, which is used to define Python methods and constants for the generated Python type. (If the [`multiple-pymethods`] feature is enabled each `#[pyclass]` is allowed to have multiple `#[pymethods]` blocks.) Finally, there may be multiple `#[pyproto]` trait implementations for the struct, which are used to define certain python magic methods such as `__str__`.
This chapter will discuss the functionality and configuration these attributes offer. Below is a list of links to the relevant section of this chapter for each:
@ -20,37 +20,87 @@ This chapter will discuss the functionality and configuration these attributes o
## Defining a new class
To define a custom Python class, a Rust struct needs to be annotated with the
`#[pyclass]` attribute.
To define a custom Python class, add the `#[pyclass]` attribute to a Rust struct or a fieldless enum.
```rust
# #![allow(dead_code)]
# use pyo3::prelude::*;
use pyo3::prelude::*;
#[pyclass]
struct MyClass {
# #[pyo3(get)]
num: i32,
struct Integer{
inner: i32
}
// A "tuple" struct
#[pyclass]
struct Number(i32);
// PyO3 supports custom discriminants in enums
#[pyclass]
enum HttpResponse {
Ok = 200,
NotFound = 404,
Teapot = 418,
// ...
}
```
Because Python objects are freely shared between threads by the Python interpreter, all structs annotated with `#[pyclass]` must implement `Send` (unless annotated with [`#[pyclass(unsendable)]`](#customizing-the-class)).
Because Python objects are freely shared between threads by the Python interpreter, all types annotated with `#[pyclass]` must implement `Send` (unless annotated with [`#[pyclass(unsendable)]`](#customizing-the-class)).
The above example generates implementations for [`PyTypeInfo`], [`PyTypeObject`], and [`PyClass`] for `MyClass`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter.
The above example generates implementations for [`PyTypeInfo`], [`PyTypeObject`], and [`PyClass`] for `MyClass` and `MyEnum`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter.
## Adding the class to a module
## Constructor
Custom Python classes can then be added to a module using `add_class()`.
By default it is not possible to create an instance of a custom class from Python code.
To declare a constructor, you need to define a method and annotate it with the `#[new]`
attribute. Only Python's `__new__` method can be specified, `__init__` is not available.
```rust
# use pyo3::prelude::*;
# #[pyclass]
# struct MyClass {
# #[allow(dead_code)]
# num: i32,
# }
# struct Number(i32);
#
#[pymethods]
impl Number {
#[new]
fn new(value: i32) -> Self {
Number(value)
}
}
```
Alternatively, if your `new` method may fail you can return `PyResult<Self>`.
```rust
# use pyo3::prelude::*;
# #[pyclass]
# struct Number(i32);
#
#[pymethods]
impl Number {
#[new]
fn new(value: i32) -> Self {
Number(value)
}
}
```
If no method marked with `#[new]` is declared, object instances can only be
created from Rust, but not from Python.
For arguments, see the `Method arguments` section below.
## Adding the class to a module
The next step is to create the module initializer and add our class to it
```rust
# use pyo3::prelude::*;
# #[pyclass]
# struct Number(i32);
#
#[pymodule]
fn mymodule(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<MyClass>()?;
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Number>()?;
Ok(())
}
```
@ -140,60 +190,14 @@ so that they can benefit from a freelist. `XXX` is a number of items for the fre
* `gc` - Classes with the `gc` parameter participate in Python garbage collection.
If a custom class contains references to other Python objects that can be collected, the [`PyGCProtocol`]({{#PYO3_DOCS_URL}}/pyo3/class/gc/trait.PyGCProtocol.html) trait has to be implemented.
* `weakref` - Adds support for Python weak references.
* `extends=BaseType` - Use a custom base class. The base `BaseType` must implement `PyTypeInfo`.
* `subclass` - Allows Python classes to inherit from this class.
* `extends=BaseType` - Use a custom base class. The base `BaseType` must implement `PyTypeInfo`. `enum` pyclasses can't use a custom base class.
* `subclass` - Allows Python classes to inherit from this class. `enum` pyclasses can't be inherited from.
* `dict` - Adds `__dict__` support, so that the instances of this type have a dictionary containing arbitrary instance variables.
* `unsendable` - Making it safe to expose `!Send` structs to Python, where all object can be accessed
by multiple threads. A class marked with `unsendable` panics when accessed by another thread.
* `module="XXX"` - Set the name of the module the class will be shown as defined in. If not given, the class
will be a virtual member of the `builtins` module.
## Constructor
By default it is not possible to create an instance of a custom class from Python code.
To declare a constructor, you need to define a method and annotate it with the `#[new]`
attribute. Only Python's `__new__` method can be specified, `__init__` is not available.
```rust
# use pyo3::prelude::*;
#[pyclass]
struct MyClass {
# #[allow(dead_code)]
num: i32,
}
#[pymethods]
impl MyClass {
#[new]
fn new(num: i32) -> Self {
MyClass { num }
}
}
```
Alternatively, if your `new` method may fail you can return `PyResult<Self>`.
```rust
# use pyo3::prelude::*;
#[pyclass]
struct MyClass {
# #[allow(dead_code)]
num: i32,
}
#[pymethods]
impl MyClass {
#[new]
fn new(num: i32) -> PyResult<Self> {
Ok(MyClass { num })
}
}
```
If no method marked with `#[new]` is declared, object instances can only be
created from Rust, but not from Python.
For arguments, see the `Method arguments` section below.
### Return type
Generally, `#[new]` method have to return `T: Into<PyClassInitializer<Self>>` or
@ -351,7 +355,7 @@ impl SubClass {
## Object properties
PyO3 supports two ways to add properties to your `#[pyclass]`:
- For simple fields with no side effects, a `#[pyo3(get, set)]` attribute can be added directly to the field definition in the `#[pyclass]`.
- For simple struct fields with no side effects, a `#[pyo3(get, set)]` attribute can be added directly to the field definition in the `#[pyclass]`.
- For properties which require computation you can define `#[getter]` and `#[setter]` functions in the [`#[pymethods]`](#instance-methods) block.
We'll cover each of these in the following sections.
@ -802,6 +806,118 @@ impl MyClass {
Note that `text_signature` on classes is not compatible with compilation in
`abi3` mode until Python 3.10 or greater.
## #[pyclass] enums
Currently PyO3 only supports fieldless enums. PyO3 adds a class attribute for each variant, so you can access them in Python without defining `#[new]`. PyO3 also provides default implementations of `__richcmp__` and `__int__`, so they can be compared using `==`:
```rust
# use pyo3::prelude::*;
#[pyclass]
enum MyEnum {
Variant,
OtherVariant,
}
Python::with_gil(|py| {
let x = Py::new(py, MyEnum::Variant).unwrap();
let y = Py::new(py, MyEnum::OtherVariant).unwrap();
let cls = py.get_type::<MyEnum>();
pyo3::py_run!(py, x y cls, r#"
assert x == cls.Variant
assert y == cls.OtherVariant
assert x != y
"#)
})
```
You can also convert your enums into `int`:
```rust
# use pyo3::prelude::*;
#[pyclass]
enum MyEnum {
Variant,
OtherVariant = 10,
}
Python::with_gil(|py| {
let cls = py.get_type::<MyEnum>();
let x = MyEnum::Variant as i32; // The exact value is assigned by the compiler.
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
"#)
})
```
PyO3 also provides `__repr__` for enums:
```rust
# use pyo3::prelude::*;
#[pyclass]
enum MyEnum{
Variant,
OtherVariant,
}
Python::with_gil(|py| {
let cls = py.get_type::<MyEnum>();
let x = Py::new(py, MyEnum::Variant).unwrap();
pyo3::py_run!(py, cls x, r#"
assert repr(x) == 'MyEnum.Variant'
assert repr(cls.OtherVariant) == 'MyEnum.OtherVariant'
"#)
})
```
All methods defined by PyO3 can be overriden. For example here's how you override `__repr__`:
```rust
# use pyo3::prelude::*;
#[pyclass]
enum MyEnum {
Answer = 42,
}
#[pymethods]
impl MyEnum {
fn __repr__(&self) -> &'static str {
"42"
}
}
Python::with_gil(|py| {
let cls = py.get_type::<MyEnum>();
pyo3::py_run!(py, cls, "assert repr(cls.Answer) == '42'")
})
```
You may not use enums as a base class or let enums inherit from other classes.
```rust,compile_fail
# use pyo3::prelude::*;
#[pyclass(subclass)]
enum BadBase{
Var1,
}
```
```rust,compile_fail
# use pyo3::prelude::*;
#[pyclass(subclass)]
struct Base;
#[pyclass(extends=Base)]
enum BadSubclass{
Var1,
}
```
`#[pyclass]` enums are currently not interoperable with `IntEnum` in Python.
## Implementation details
The `#[pyclass]` macros rely on a lot of conditional code generation: each `#[pyclass]` can optionally have a `#[pymethods]` block as well as several different possible `#[pyproto]` trait implementations.
@ -849,55 +965,20 @@ impl pyo3::IntoPy<PyObject> for MyClass {
}
}
impl pyo3::class::impl_::PyClassImpl for MyClass {
impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
const DOC: &'static str = "Class for demonstration\u{0}";
const IS_GC: bool = false;
const IS_BASETYPE: bool = false;
const IS_SUBCLASS: bool = false;
type Layout = PyCell<MyClass>;
type BaseType = PyAny;
type ThreadChecker = pyo3::class::impl_::ThreadCheckerStub<MyClass>;
type ThreadChecker = pyo3::impl_::pyclass::ThreadCheckerStub<MyClass>;
fn for_each_method_def(visitor: &mut dyn FnMut(&[pyo3::class::PyMethodDefType])) {
use pyo3::class::impl_::*;
fn for_all_items(visitor: &mut dyn FnMut(&pyo3::impl_::pyclass::PyClassItems)) {
use pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<MyClass>::new();
static INTRINSIC_ITEMS: PyClassItems = PyClassItems { slots: &[], methods: &[] };
visitor(&INTRINSIC_ITEMS);
visitor(collector.py_methods());
visitor(collector.py_class_descriptors());
visitor(collector.object_protocol_methods());
visitor(collector.async_protocol_methods());
visitor(collector.descr_protocol_methods());
visitor(collector.mapping_protocol_methods());
visitor(collector.number_protocol_methods());
}
fn get_new() -> Option<pyo3::ffi::newfunc> {
use pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
collector.new_impl()
}
fn get_alloc() -> Option<pyo3::ffi::allocfunc> {
use pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
collector.alloc_impl()
}
fn get_free() -> Option<pyo3::ffi::freefunc> {
use pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
collector.free_impl()
}
fn for_each_proto_slot(visitor: &mut dyn FnMut(&[pyo3::ffi::PyType_Slot])) {
// Implementation which uses dtolnay specialization to load all slots.
use pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
visitor(collector.object_protocol_slots());
visitor(collector.number_protocol_slots());
visitor(collector.iter_protocol_slots());
visitor(collector.gc_protocol_slots());
visitor(collector.descr_protocol_slots());
visitor(collector.mapping_protocol_slots());
visitor(collector.sequence_protocol_slots());
visitor(collector.async_protocol_slots());
visitor(collector.buffer_protocol_slots());
visitor(collector.methods_protocol_slots());
}
}
# Python::with_gil(|py| {

449
guide/src/class/numeric.md Normal file
View file

@ -0,0 +1,449 @@
# Emulating numeric types
At this point we have a `Number` class that we can't actually do any math on!
Before proceeding, we should think about how we want to handle overflows. There are three obvious solutions:
- We can have infinite precision just like Python's `int`. However that would be quite boring - we'd
be reinventing the wheel.
- We can raise exceptions whenever `Number` overflows, but that makes the API painful to use.
- We can wrap around the boundary of `i32`. This is the approach we'll take here. To do that we'll just forward to `i32`'s
`wrapping_*` methods.
### Fixing our constructor
Let's address the first overflow, in `Number`'s constructor:
```python
from my_module import Number
n = Number(1 << 1337)
```
```text
Traceback (most recent call last):
File "example.py", line 3, in <module>
n = Number(1 << 1337)
OverflowError: Python int too large to convert to C long
```
Instead of relying on the default [`FromPyObject`] extraction to parse arguments, we can specify our
own extraction function, using the `#[pyo3(from_py_with = "...")]` attribute. Unfortunately PyO3
doesn't provide a way to wrap Python integers out of the box, but we can do a Python call to mask it
and cast it to an `i32`.
```rust
# #![allow(dead_code)]
use pyo3::prelude::*;
fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
let val: u32 = val.extract()?;
// 👇 This intentionally overflows!
Ok(val as i32)
}
```
We also add documentation, via `///` comments and the `#[pyo3(text_signature = "...")]` attribute, both of which are visible to Python users.
```rust
# #![allow(dead_code)]
use pyo3::prelude::*;
fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
let val: u32 = val.extract()?;
Ok(val as i32)
}
/// Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.
/// It's not a story C would tell you. It's a Rust legend.
#[pyclass(module = "my_module")]
#[pyo3(text_signature = "(int)")]
struct Number(i32);
#[pymethods]
impl Number {
#[new]
fn new(#[pyo3(from_py_with = "wrap")] value: i32) -> Self {
Self(value)
}
}
```
With that out of the way, let's implement some operators:
```rust
use std::convert::TryInto;
use pyo3::exceptions::{PyZeroDivisionError, PyValueError};
# use pyo3::prelude::*;
#
# #[pyclass]
# struct Number(i32);
#
#[pymethods]
impl Number {
fn __add__(&self, other: &Self) -> Self {
Self(self.0.wrapping_add(other.0))
}
fn __sub__(&self, other: &Self) -> Self {
Self(self.0.wrapping_sub(other.0))
}
fn __mul__(&self, other: &Self) -> Self {
Self(self.0.wrapping_mul(other.0))
}
fn __truediv__(&self, other: &Self) -> PyResult<Self> {
match self.0.checked_div(other.0) {
Some(i) => Ok(Self(i)),
None => Err(PyZeroDivisionError::new_err("division by zero")),
}
}
fn __floordiv__(&self, other: &Self) -> PyResult<Self> {
match self.0.checked_div(other.0) {
Some(i) => Ok(Self(i)),
None => Err(PyZeroDivisionError::new_err("division by zero")),
}
}
fn __rshift__(&self, other: &Self) -> PyResult<Self> {
match other.0.try_into() {
Ok(rhs) => Ok(Self(self.0.wrapping_shr(rhs))),
Err(_) => Err(PyValueError::new_err("negative shift count")),
}
}
fn __lshift__(&self, other: &Self) -> PyResult<Self> {
match other.0.try_into() {
Ok(rhs) => Ok(Self(self.0.wrapping_shl(rhs))),
Err(_) => Err(PyValueError::new_err("negative shift count")),
}
}
}
```
### Unary arithmethic operations
```rust
# use pyo3::prelude::*;
#
# #[pyclass]
# struct Number(i32);
#
#[pymethods]
impl Number {
fn __pos__(slf: PyRef<Self>) -> PyRef<Self> {
slf
}
fn __neg__(&self) -> Self {
Self(-self.0)
}
fn __abs__(&self) -> Self {
Self(self.0.abs())
}
fn __invert__(&self) -> Self {
Self(!self.0)
}
}
```
### Support for the `complex()`, `int()` and `float()` built-in functions.
```rust
# use pyo3::prelude::*;
#
# #[pyclass]
# struct Number(i32);
#
use pyo3::types::PyComplex;
#[pymethods]
impl Number {
fn __int__(&self) -> i32 {
self.0
}
fn __float__(&self) -> f64 {
self.0 as f64
}
fn __complex__<'py>(&self, py: Python<'py>) -> &'py PyComplex {
PyComplex::from_doubles(py, self.0 as f64, 0.0)
}
}
```
We do not implement the in-place operations like `__iadd__` because we do not wish to mutate `Number`.
Similarly we're not interested in supporting operations with different types, so we do not implement
the reflected operations like `__radd__` either.
Now Python can use our `Number` class:
```python
from my_module import Number
def hash_djb2(s: str):
'''
A version of Daniel J. Bernstein's djb2 string hashing algorithm
Like many hashing algorithms, it relies on integer wrapping.
'''
n = Number(0)
five = Number(5)
for x in s:
n = Number(ord(x)) + ((n << five) - n)
return n
assert hash_djb2('l50_50') == Number(-1152549421)
```
### Final code
```rust
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::convert::TryInto;
use pyo3::exceptions::{PyValueError, PyZeroDivisionError};
use pyo3::prelude::*;
use pyo3::class::basic::CompareOp;
use pyo3::types::PyComplex;
fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
let val: u32 = val.extract()?;
Ok(val as i32)
}
/// Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.
/// It's not a story C would tell you. It's a Rust legend.
#[pyclass(module = "my_module")]
#[pyo3(text_signature = "(int)")]
struct Number(i32);
#[pymethods]
impl Number {
#[new]
fn new(#[pyo3(from_py_with = "wrap")] value: i32) -> Self {
Self(value)
}
fn __repr__(&self) -> String {
format!("Number({})", self.0)
}
fn __str__(&self) -> String {
self.0.to_string()
}
fn __hash__(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.0.hash(&mut hasher);
hasher.finish()
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Lt => Ok(self.0 < other.0),
CompareOp::Le => Ok(self.0 <= other.0),
CompareOp::Eq => Ok(self.0 == other.0),
CompareOp::Ne => Ok(self.0 != other.0),
CompareOp::Gt => Ok(self.0 > other.0),
CompareOp::Ge => Ok(self.0 >= other.0),
}
}
fn __bool__(&self) -> bool {
self.0 != 0
}
fn __add__(&self, other: &Self) -> Self {
Self(self.0.wrapping_add(other.0))
}
fn __sub__(&self, other: &Self) -> Self {
Self(self.0.wrapping_sub(other.0))
}
fn __mul__(&self, other: &Self) -> Self {
Self(self.0.wrapping_mul(other.0))
}
fn __truediv__(&self, other: &Self) -> PyResult<Self> {
match self.0.checked_div(other.0) {
Some(i) => Ok(Self(i)),
None => Err(PyZeroDivisionError::new_err("division by zero")),
}
}
fn __floordiv__(&self, other: &Self) -> PyResult<Self> {
match self.0.checked_div(other.0) {
Some(i) => Ok(Self(i)),
None => Err(PyZeroDivisionError::new_err("division by zero")),
}
}
fn __rshift__(&self, other: &Self) -> PyResult<Self> {
match other.0.try_into() {
Ok(rhs) => Ok(Self(self.0.wrapping_shr(rhs))),
Err(_) => Err(PyValueError::new_err("negative shift count")),
}
}
fn __lshift__(&self, other: &Self) -> PyResult<Self> {
match other.0.try_into() {
Ok(rhs) => Ok(Self(self.0.wrapping_shl(rhs))),
Err(_) => Err(PyValueError::new_err("negative shift count")),
}
}
fn __xor__(&self, other: &Self) -> Self {
Self(self.0 ^ other.0)
}
fn __or__(&self, other: &Self) -> Self {
Self(self.0 | other.0)
}
fn __and__(&self, other: &Self) -> Self {
Self(self.0 & other.0)
}
fn __int__(&self) -> i32 {
self.0
}
fn __float__(&self) -> f64 {
self.0 as f64
}
fn __complex__<'py>(&self, py: Python<'py>) -> &'py PyComplex {
PyComplex::from_doubles(py, self.0 as f64, 0.0)
}
}
#[pymodule]
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Number>()?;
Ok(())
}
# const SCRIPT: &'static str = r#"
# def hash_djb2(s: str):
# n = Number(0)
# five = Number(5)
#
# for x in s:
# n = Number(ord(x)) + ((n << five) - n)
# return n
#
# assert hash_djb2('l50_50') == Number(-1152549421)
# assert hash_djb2('logo') == Number(3327403)
# assert hash_djb2('horizon') == Number(1097468315)
#
#
# assert Number(2) + Number(2) == Number(4)
# assert Number(2) + Number(2) != Number(5)
#
# assert Number(13) - Number(7) == Number(6)
# assert Number(13) - Number(-7) == Number(20)
#
# assert Number(13) / Number(7) == Number(1)
# assert Number(13) // Number(7) == Number(1)
#
# assert Number(13) * Number(7) == Number(13*7)
#
# assert Number(13) > Number(7)
# assert Number(13) < Number(20)
# assert Number(13) == Number(13)
# assert Number(13) >= Number(7)
# assert Number(13) <= Number(20)
# assert Number(13) == Number(13)
#
#
# assert (True if Number(1) else False)
# assert (False if Number(0) else True)
#
#
# assert int(Number(13)) == 13
# assert float(Number(13)) == 13
# assert Number.__doc__ == "Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.\nIt's not a story C would tell you. It's a Rust legend."
# assert Number(12345234523452) == Number(1498514748)
# try:
# import inspect
# assert inspect.signature(Number).__str__() == '(int)'
# except ValueError:
# # Not supported with `abi3` before Python 3.10
# pass
# assert Number(1337).__str__() == '1337'
# assert Number(1337).__repr__() == 'Number(1337)'
"#;
#
# use pyo3::type_object::PyTypeObject;
#
# fn main() -> PyResult<()> {
# Python::with_gil(|py| -> PyResult<()> {
# let globals = PyModule::import(py, "__main__")?.dict();
# globals.set_item("Number", Number::type_object(py))?;
#
# py.run(SCRIPT, Some(globals), None)?;
# Ok(())
# })
# }
```
## Appendix: Writing some unsafe code
At the beginning of this chapter we said that PyO3 doesn't provide a way to wrap Python integers out
of the box but that's a half truth. There's not a PyO3 API for it, but there's a Python C API
function that does:
```c
unsigned long PyLong_AsUnsignedLongMask(PyObject *obj)
```
We can call this function from Rust by using [`pyo3::ffi::PyLong_AsUnsignedLongMask`]. This is an *unsafe*
function, which means we have to use an unsafe block to call it and take responsibility for upholding
the contracts of this function. Let's review those contracts:
- The GIL must be held. If it's not, calling this function causes a data race.
- The pointer must be valid, i.e. it must be properly aligned and point to a valid Python object.
Let's create that helper function. The signature has to be `fn(&PyAny) -> PyResult<T>`.
- `&PyAny` represents a checked borrowed reference, so the pointer derived from it is valid (and not null).
- Whenever we have borrowed references to Python objects in scope, it is guaranteed that the GIL is held. This reference is also where we can get a [`Python`] token to use in our call to [`PyErr::take`].
```rust
# #![allow(dead_code)]
use std::os::raw::c_ulong;
use pyo3::prelude::*;
use pyo3::ffi;
use pyo3::conversion::AsPyPointer;
fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
let py: Python = obj.py();
unsafe {
let ptr = obj.as_ptr();
let ret: c_ulong = ffi::PyLong_AsUnsignedLongMask(ptr);
if ret == c_ulong::MAX {
if let Some(err) = PyErr::take(py) {
return Err(err);
}
}
Ok(ret as i32)
}
}
```
[`PyErr::take`]: https://docs.rs/pyo3/latest/pyo3/prelude/struct.PyErr.html#method.take
[`Python`]: https://docs.rs/pyo3/latest/pyo3/struct.Python.html
[`FromPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.FromPyObject.html
[`pyo3::ffi::PyLong_AsUnsignedLongMask`]: https://docs.rs/pyo3/latest/pyo3/ffi/fn.PyLong_AsUnsignedLongMask.html

232
guide/src/class/object.md Normal file
View file

@ -0,0 +1,232 @@
# Basic object customization
Recall the `Number` class from the previous chapter:
```rust
use pyo3::prelude::*;
#[pyclass]
struct Number(i32);
#[pymethods]
impl Number {
#[new]
fn new(value: i32) -> Self {
Self(value)
}
}
#[pymodule]
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Number>()?;
Ok(())
}
```
At this point Python code can import the module, access the class and create class instances - but
nothing else.
```python
from my_module import Number
n = Number(5)
print(n)
```
```text
<builtins.Number object at 0x000002B4D185D7D0>
```
### String representations
It can't even print an user-readable representation of itself! We can fix that by defining the
`__repr__` and `__str__` methods inside a `#[pymethods]` block. We do this by accessing the value
contained inside `Number`.
```rust
# use pyo3::prelude::*;
#
# #[pyclass]
# struct Number(i32);
#
#[pymethods]
impl Number {
// For `__repr__` we want to return a string that Python code could use to recreate
// the `Number`, like `Number(5)` for example.
fn __repr__(&self) -> String {
// We use the `format!` macro to create a string. Its first argument is a
// format string, followed by any number of parameters which replace the
// `{}`'s in the format string.
//
// 👇 Tuple field access in Rust uses a dot
format!("Number({})", self.0)
}
// `__str__` is generally used to create an "informal" representation, so we
// just forward to `i32`'s `ToString` trait implementation to print a bare number.
fn __str__(&self) -> String {
self.0.to_string()
}
}
```
### Hashing
Let's also implement hashing. We'll just hash the `i32`. For that we need a [`Hasher`]. The one
provided by `std` is [`DefaultHasher`], which uses the [SipHash] algorithm.
```rust
use std::collections::hash_map::DefaultHasher;
// Required to call the `.hash` and `.finish` methods, which are defined on traits.
use std::hash::{Hash, Hasher};
# use pyo3::prelude::*;
#
# #[pyclass]
# struct Number(i32);
#
#[pymethods]
impl Number {
fn __hash__(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.0.hash(&mut hasher);
hasher.finish()
}
}
```
> **Note**: When implementing `__hash__` and comparisons, it is important that the following property holds:
>
> ```text
> k1 == k2 -> hash(k1) == hash(k2)
> ```
>
> In other words, if two keys are equal, their hashes must also be equal. In addition you must take
> care that your classes' hash doesn't change during its lifetime. In this tutorial we do that by not
> letting Python code change our `Number` class. In other words, it is immutable.
>
> By default, all `#[pyclass]` types have a default hash implementation from Python.
> Types which should not be hashable can override this by setting `__hash__` to None.
> This is the same mechanism as for a pure-Python class. This is done like so:
>
> ```rust
> # use pyo3::prelude::*;
> #[pyclass]
> struct NotHashable { }
>
> #[pymethods]
> impl NotHashable {
> #[classattr]
> const __hash__: Option<Py<PyAny>> = None;
>}
> ```
### Comparisons
Unlike in Python, PyO3 does not provide the magic comparison methods you might expect like `__eq__`,
`__lt__` and so on. Instead you have to implement all six operations at once with `__richcmp__`.
This method will be called with a value of `CompareOp` depending on the operation.
```rust
use pyo3::class::basic::CompareOp;
# use pyo3::prelude::*;
#
# #[pyclass]
# struct Number(i32);
#
#[pymethods]
impl Number {
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Lt => Ok(self.0 < other.0),
CompareOp::Le => Ok(self.0 <= other.0),
CompareOp::Eq => Ok(self.0 == other.0),
CompareOp::Ne => Ok(self.0 != other.0),
CompareOp::Gt => Ok(self.0 > other.0),
CompareOp::Ge => Ok(self.0 >= other.0),
}
}
}
```
### Truthyness
We'll consider `Number` to be `True` if it is nonzero:
```rust
# use pyo3::prelude::*;
#
# #[pyclass]
# struct Number(i32);
#
#[pymethods]
impl Number {
fn __bool__(&self) -> bool {
self.0 != 0
}
}
```
### Final code
```rust
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use pyo3::prelude::*;
use pyo3::class::basic::CompareOp;
#[pyclass]
struct Number(i32);
#[pymethods]
impl Number {
#[new]
fn new(value: i32) -> Self {
Self(value)
}
fn __repr__(&self) -> String {
format!("Number({})", self.0)
}
fn __str__(&self) -> String {
self.0.to_string()
}
fn __hash__(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.0.hash(&mut hasher);
hasher.finish()
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Lt => Ok(self.0 < other.0),
CompareOp::Le => Ok(self.0 <= other.0),
CompareOp::Eq => Ok(self.0 == other.0),
CompareOp::Ne => Ok(self.0 != other.0),
CompareOp::Gt => Ok(self.0 > other.0),
CompareOp::Ge => Ok(self.0 >= other.0),
}
}
fn __bool__(&self) -> bool {
self.0 != 0
}
}
#[pymodule]
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Number>()?;
Ok(())
}
```
[`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html
[`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

View file

@ -18,7 +18,6 @@ In PyO3 0.15, if a function name in `#[pymethods]` is a recognised magic method,
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 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 for the sequence protocol
When PyO3 handles a magic method, a couple of changes apply compared to other `#[pymethods]`:
- The `#[pyo3(text_signature = "...")]` attribute is not allowed
@ -86,11 +85,7 @@ given signatures should be interpreted as follows:
- `__aiter__(<self>) -> object`
- `__anext__(<self>) -> Option<object> or IterANextOutput`
#### Sequence types
TODO; see [#1884](https://github.com/PyO3/pyo3/issues/1884)
#### Mapping types
#### Mapping & Sequence types
- `__len__(<self>) -> usize`
- `__contains__(<self>, object) -> bool`
@ -180,7 +175,8 @@ TODO; see [#1884](https://github.com/PyO3/pyo3/issues/1884)
#### Garbage Collector Integration
TODO; see [#1884](https://github.com/PyO3/pyo3/issues/1884)
- `__traverse__(<self>, visit: pyo3::class::gc::PyVisit) -> Result<(), pyo3::class::gc::PyTraverseError>`
- `__clear__(<self>) -> ()`
### `#[pyproto]` traits
@ -449,19 +445,6 @@ impl PyGCProtocol for ClassWithGCSupport {
}
```
Special protocol trait implementations have to be annotated with the `#[pyproto]` attribute.
It is also possible to enable GC for custom classes using the `gc` parameter of the `pyclass` attribute.
i.e. `#[pyclass(gc)]`. In that case instances of custom class participate in Python garbage
collection, and it is possible to track them with `gc` module methods. When using the `gc` parameter,
it is *required* to implement the `PyGCProtocol` trait, failure to do so will result in an error
at compile time:
```compile_fail
#[pyclass(gc)]
struct GCTracked {} // Fails because it does not implement PyGCProtocol
```
#### Iterator Types
Iterators can be defined using the

View file

@ -65,11 +65,15 @@ Most users should only need a single `#[pymethods]` per `#[pyclass]`. In additio
See [the `#[pyclass]` implementation details](class.md#implementation-details) for more information.
### `pyproto`
This feature enables the `#[pyproto]` macro, which is an alternative (older, soon-to-be-deprecated) to `#[pymethods]` for defining magic methods such as `__eq__`.
> This feature is enabled by default. To disable it, set `default-features = false` for the `pyo3` entry in your Cargo.toml.
### `nightly`
The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use Rust's unstable specialization feature to apply the following optimizations:
- `FromPyObject` for `Vec` and `[T;N]` can perform a `memcpy` when the object supports the Python buffer protocol.
- `ToBorrowedObject` can skip a reference count increase when the provided object is a Python native type.
The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use the auto_traits and negative_impls features to fix the `Python::allow_threads` function.
### `resolve-config`

View file

@ -5,10 +5,40 @@ For a detailed list of all changes, see the [CHANGELOG](changelog.md).
## from 0.15.* to 0.16
### Drop support for older technogies
### Drop support for older technologies
PyO3 0.16 has increased minimum Rust version to 1.48 and minimum Python version to 3.7. This enables ore use of newer language features (enabling some of the other additions in 0.16) and simplifies maintenance of the project.
### Container magic methods now match Python behavior
In PyO3 0.15, `__getitem__`, `__setitem__` and `__delitem__` in `#[pymethods]` would generate only the _mapping_ implementation for a `#[pyclass]`. To match the Python behavior, these methods now generate both the _mapping_ **and** _sequence_ implementations.
This means that classes implementing these `#[pymethods]` will now also be treated as sequences, same as a Python `class` would be. Small differences in behavior may result:
- PyO3 will allow instances of these classes to be cast to `PySequence` as well as `PyMapping`.
- Python will provide a default implementation of `__iter__` (if the class did not have one) which repeatedly calls `__getitem__` with integers (starting at 0) until an `IndexError` is raised.
To explain this in detail, consider the following Python class:
```python
class ExampleContainer:
def __len__(self):
return 5
def __getitem__(self, idx: int) -> int:
if idx < 0 or idx > 5:
raise IndexError()
return idx
```
This class implements a Python [sequence](https://docs.python.org/3/glossary.html#term-sequence).
The `__len__` and `__getitem__` methods are also used to implement a Python [mapping](https://docs.python.org/3/glossary.html#term-mapping). In the Python C-API, these methods are not shared: the sequence `__len__` and `__getitem__` are defined by the `sq_len` and `sq_item` slots, and the mapping equivalents are `mp_len` and `mp_subscript`. There are similar distinctions for `__setitem__` and `__delitem__`.
Because there is no such distinction from Python, implementing these methods will fill the mapping and sequence slots simultaneously. A Python class with `__len__` implemented, for example, will have both the `sq_len` and `mp_len` slots filled.
The PyO3 behavior in 0.16 has been changed to be closer to this Python behavior by default.
## from 0.14.* to 0.15
### Changes in sequence indexing

88
noxfile.py Normal file
View file

@ -0,0 +1,88 @@
import time
from glob import glob
import nox
nox.options.sessions = ["test", "clippy", "fmt"]
@nox.session(venv_backend="none")
def test(session: nox.Session):
test_rust(session)
test_py(session)
@nox.session(name="test-rust", venv_backend="none")
def test_rust(session: nox.Session):
session.run("cargo", "test", external=True)
session.run("cargo", "test", "--features=abi3", external=True)
session.run("cargo", "test", "--features=full", external=True)
session.run("cargo", "test", "--features=abi3 full", external=True)
@nox.session(name="test-py", venv_backend="none")
def test_py(session):
session.run("nox", "-f", "pytests/noxfile.py", external=True)
for example in glob("examples/*/noxfile.py"):
session.run("nox", "-f", example, external=True)
@nox.session
def fmt(session: nox.Session):
fmt_rust(session)
fmt_py(session)
@nox.session(name="fmt-rust", venv_backend="none")
def fmt_rust(session: nox.Session):
session.run("cargo", "fmt", "--all", "--check", external=True)
@nox.session(name="fmt-py")
def fmt_py(session: nox.Session):
session.install("black==22.1.0")
session.run("black", ".", "--check")
@nox.session(venv_backend="none")
def clippy(session: nox.Session) -> None:
for feature_set in ["full", "abi3 full"]:
session.run(
"cargo",
"clippy",
f"--features={feature_set}",
"--all-targets",
"--workspace",
"--",
"--deny=warnings",
external=True,
)
@nox.session(venv_backend="none")
def publish(session: nox.Session) -> None:
session.run(
"cargo",
"publish",
"--manifest-path",
"pyo3-build-config/Cargo.toml",
external=True,
)
time.sleep(10)
session.run(
"cargo",
"publish",
"--manifest-path",
"pyo3-macros-backend/Cargo.toml",
external=True,
)
time.sleep(10)
session.run(
"cargo", "publish", "--manifest-path", "pyo3-macros/Cargo.toml", external=True
)
time.sleep(10)
session.run(
"cargo", "publish", "--manifest-path", "pyo3-ffi/Cargo.toml", external=True
)
time.sleep(10)
session.run("cargo", "publish", external=True)

View file

@ -10,6 +10,7 @@ mod impl_;
#[cfg(feature = "resolve-config")]
use std::io::Cursor;
use std::{env, process::Command};
#[cfg(feature = "resolve-config")]
use once_cell::sync::OnceCell;
@ -115,6 +116,36 @@ fn abi3_config() -> InterpreterConfig {
interpreter_config
}
/// Use certain features if we detect the compiler being used supports them.
///
/// Features may be removed or added as MSRV gets bumped or new features become available,
/// 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 generics on Rust 1.51 and greater
if rustc_minor_version >= 51 {
println!("cargo:rustc-cfg=min_const_generics");
}
// Enable use of std::ptr::addr_of! on Rust 1.51 and greater
if rustc_minor_version >= 51 {
println!("cargo:rustc-cfg=addr_of");
}
}
/// Private exports used in PyO3's build.rs
///
/// Please don't use these - they could change at any time.

39
pyo3-ffi/Cargo.toml Normal file
View file

@ -0,0 +1,39 @@
[package]
name = "pyo3-ffi"
version = "0.15.1"
description = "Python-API bindings for the PyO3 ecosystem"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"]
homepage = "https://github.com/pyo3/pyo3"
repository = "https://github.com/pyo3/pyo3"
categories = ["api-bindings", "development-tools::ffi"]
license = "Apache-2.0"
edition = "2018"
[dependencies]
libc = "0.2.62"
[features]
default = []
# Use this feature when building an extension module.
# It tells the linker to keep the python symbols unresolved,
# so that the module can also be used with statically linked python interpreters.
extension-module = []
# Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more.
abi3 = ["pyo3-build-config/abi3"]
# With abi3, we can manually set the minimum Python version.
abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37"]
abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38"]
abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39"]
abi3-py310 = ["abi3", "pyo3-build-config/abi3-py310"]
[build-dependencies]
pyo3-build-config = { path = "../pyo3-build-config", version = "0.15.1", features = ["resolve-config"] }

199
pyo3-ffi/README.md Normal file
View file

@ -0,0 +1,199 @@
# pyo3-ffi
This crate provides [Rust](https://www.rust-lang.org/) FFI declarations for Python 3.
It supports both the stable and the unstable component of the ABI through the use of cfg flags.
Python Versions 3.7+ are supported.
It is meant for advanced users only - regular PyO3 users shouldn't
need to interact with this crate at all.
The contents of this crate are not documented here, as it would entail
basically copying the documentation from CPython. Consult the [Python/C API Reference
Manual][capi] for up-to-date documentation.
# Minimum supported Rust and Python versions
PyO3 supports the following software versions:
- Python 3.7 and up (CPython and PyPy)
- Rust 1.48 and up
# Example: Building Python Native modules
PyO3 can be used to generate a native Python module. The easiest way to try this out for the
first time is to use [`maturin`]. `maturin` is a tool for building and publishing Rust-based
Python packages with minimal configuration. The following steps set up some files for an example
Python module, install `maturin`, and then show how to build and import the Python module.
First, create a new folder (let's call it `string_sum`) containing the following two files:
**`Cargo.toml`**
```toml
[lib]
name = "string_sum"
# "cdylib" is necessary to produce a shared library for Python to import from.
#
# Downstream Rust code (including code in `bin/`, `examples/`, and `tests/`) will not be able
# to `use string_sum;` unless the "rlib" or "lib" crate type is also included, e.g.:
# crate-type = ["cdylib", "rlib"]
crate-type = ["cdylib"]
[dependencies.pyo3-ffi]
version = "*"
features = ["extension-module"]
```
**`src/lib.rs`**
```rust
use std::os::raw::c_char;
use std::ptr;
use pyo3_ffi::*;
#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject {
let init = PyModuleDef {
m_base: PyModuleDef_HEAD_INIT,
m_name: "string_sum\0".as_ptr() as *const c_char,
m_doc: std::ptr::null(),
m_size: 0,
m_methods: std::ptr::null_mut(),
m_slots: std::ptr::null_mut(),
m_traverse: None,
m_clear: None,
m_free: None,
};
let mptr = PyModule_Create(Box::into_raw(Box::new(init)));
let version = env!("CARGO_PKG_VERSION");
PyModule_AddObject(
mptr,
"__version__\0".as_ptr() as *const c_char,
PyUnicode_FromStringAndSize(version.as_ptr() as *const c_char, version.len() as isize),
);
let wrapped_sum_as_string = PyMethodDef {
ml_name: "sum_as_string\0".as_ptr() as *const c_char,
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() as *const c_char,
};
// PyModule_AddObject can technically fail.
// For more involved applications error checking may be necessary
PyModule_AddObject(
mptr,
"sum_as_string\0".as_ptr() as *const c_char,
PyCFunction_NewEx(
Box::into_raw(Box::new(wrapped_sum_as_string)),
std::ptr::null_mut(),
PyUnicode_InternFromString("string_sum\0".as_ptr() as *const c_char),
),
);
let all = ["__all__\0", "__version__\0", "sum_as_string\0"];
let pyall = PyTuple_New(all.len() as isize);
for (i, obj) in all.iter().enumerate() {
PyTuple_SET_ITEM(
pyall,
i as isize,
PyUnicode_InternFromString(obj.as_ptr() as *const c_char),
)
}
PyModule_AddObject(mptr, "__all__\0".as_ptr() as *const c_char, pyall);
mptr
}
pub unsafe extern "C" fn sum_as_string(
_self: *mut PyObject,
args: *mut *mut PyObject,
nargs: Py_ssize_t,
) -> *mut PyObject {
if nargs != 2 {
return raise_type_error("sum_as_string() expected 2 positional arguments");
}
let arg1 = *args;
if PyLong_Check(arg1) == 0 {
return raise_type_error("sum_as_string() expected an int for positional argument 1");
}
let arg1 = PyLong_AsLong(arg1);
if !PyErr_Occurred().is_null() {
return ptr::null_mut()
}
let arg2 = *args.add(1);
if PyLong_Check(arg2) == 0 {
return raise_type_error("sum_as_string() expected an int for positional argument 2");
}
let arg2 = PyLong_AsLong(arg2);
if !PyErr_Occurred().is_null() {
return ptr::null_mut()
}
let res = (arg1 + arg2).to_string();
PyUnicode_FromStringAndSize(res.as_ptr() as *const c_char, res.len() as isize)
}
#[cold]
#[inline(never)]
fn raise_type_error(msg: &str) -> *mut PyObject {
unsafe {
let err_msg =
PyUnicode_FromStringAndSize(msg.as_ptr() as *const c_char, msg.len() as isize);
PyErr_SetObject(PyExc_TypeError, err_msg);
Py_DECREF(err_msg);
};
std::ptr::null_mut()
}
```
With those two files in place, now `maturin` needs to be installed. This can be done using
Python's package manager `pip`. First, load up a new Python `virtualenv`, and install `maturin`
into it:
```bash
$ cd string_sum
$ python -m venv .env
$ source .env/bin/activate
$ pip install maturin
```
Now build and execute the module:
```bash
$ maturin develop
# lots of progress output as maturin runs the compilation...
$ python
>>> import string_sum
>>> string_sum.sum_as_string(5, 20)
'25'
```
As well as with `maturin`, it is possible to build using [setuptools-rust] or
[manually][manual_builds]. Both offer more flexibility than `maturin` but require further
configuration.
While most projects use the safe wrapper provided by PyO3,
you can take a look at the [`orjson`] library as an example on how to use `pyo3-ffi` directly.
For those well versed in C and Rust the [tutorials] from the CPython documentation
can be easily converted to rust as well.
[tutorials]: https://docs.python.org/3/extending/
[`orjson`]: https://github.com/ijl/orjson
[capi]: https://docs.python.org/3/c-api/index.html
[`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"
[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"

122
pyo3-ffi/build.rs Normal file
View file

@ -0,0 +1,122 @@
use pyo3_build_config::{
bail, ensure, print_feature_cfgs,
pyo3_build_script_impl::{
cargo_env_var, env_var, errors::Result, resolve_interpreter_config, InterpreterConfig,
PythonVersion,
},
};
/// Minimum Python version PyO3 supports.
const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 7 };
fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> {
ensure!(
interpreter_config.version >= MINIMUM_SUPPORTED_VERSION,
"the configured Python interpreter version ({}) is lower than PyO3's minimum supported version ({})",
interpreter_config.version,
MINIMUM_SUPPORTED_VERSION,
);
Ok(())
}
fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result<()> {
if let Some(pointer_width) = interpreter_config.pointer_width {
// Try to check whether the target architecture matches the python library
let rust_target = match cargo_env_var("CARGO_CFG_TARGET_POINTER_WIDTH")
.unwrap()
.as_str()
{
"64" => 64,
"32" => 32,
x => bail!("unexpected Rust target pointer width: {}", x),
};
ensure!(
rust_target == pointer_width,
"your Rust target architecture ({}-bit) does not match your python interpreter ({}-bit)",
rust_target,
pointer_width
);
}
Ok(())
}
fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> {
let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap();
let is_extension_module = cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some();
if target_os == "windows" || target_os == "android" || !is_extension_module {
// windows and android - always link
// other systems - only link if not extension module
println!(
"cargo:rustc-link-lib={link_model}{alias}{lib_name}",
link_model = if interpreter_config.shared {
""
} else {
"static="
},
alias = if target_os == "windows" {
"pythonXY:"
} else {
""
},
lib_name = interpreter_config.lib_name.as_ref().ok_or(
"attempted to link to Python shared library but config does not contain lib_name"
)?,
);
if let Some(lib_dir) = &interpreter_config.lib_dir {
println!("cargo:rustc-link-search=native={}", lib_dir);
}
}
Ok(())
}
/// Prepares the PyO3 crate for compilation.
///
/// This loads the config from pyo3-build-config and then makes some additional checks to improve UX
/// for users.
///
/// Emits the cargo configuration based on this config as well as a few checks of the Rust compiler
/// version to enable features which aren't supported on MSRV.
fn configure_pyo3() -> Result<()> {
let interpreter_config = resolve_interpreter_config()?;
if env_var("PYO3_PRINT_CONFIG").map_or(false, |os_str| os_str == "1") {
print_config_and_exit(&interpreter_config);
}
ensure_python_version(&interpreter_config)?;
ensure_target_pointer_width(&interpreter_config)?;
if !interpreter_config.suppress_build_script_link_lines {
emit_link_config(&interpreter_config)?;
}
interpreter_config.emit_pyo3_cfgs();
// Extra lines come last, to support last write wins.
for line in &interpreter_config.extra_build_script_lines {
println!("{}", line);
}
// Emit cfgs like `addr_of` and `min_const_generics`
print_feature_cfgs();
Ok(())
}
fn print_config_and_exit(config: &InterpreterConfig) {
println!("\n-- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile --");
config
.to_writer(&mut std::io::stdout())
.expect("failed to print config to stdout");
std::process::exit(101);
}
fn main() {
if let Err(e) = configure_pyo3() {
eprintln!("error: {}", e.report());
std::process::exit(1)
}
}

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int};
use std::ptr;
@ -96,10 +96,9 @@ extern "C" {
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
#[inline]
pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int {
(match (*crate::ffi::Py_TYPE(o)).tp_iternext {
(match (*crate::Py_TYPE(o)).tp_iternext {
Some(tp_iternext) => {
tp_iternext as *const std::os::raw::c_void
!= crate::ffi::_PyObject_NextNotImplemented as _
tp_iternext as *const std::os::raw::c_void != crate::_PyObject_NextNotImplemented as _
}
None => false,
}) as c_int

View file

@ -1,4 +1,4 @@
use crate::ffi::object::PyTypeObject;
use crate::object::PyTypeObject;
#[cfg_attr(windows, link(name = "pythonXY"))]
extern "C" {

View file

@ -1,5 +1,5 @@
use crate::ffi::longobject::PyLongObject;
use crate::ffi::object::*;
use crate::longobject::PyLongObject;
use crate::object::*;
use std::os::raw::{c_int, c_long};
#[cfg_attr(windows, link(name = "pythonXY"))]
@ -10,7 +10,7 @@ extern "C" {
#[inline]
pub unsafe fn PyBool_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyBool_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyBool_Type)) as c_int
}
#[cfg_attr(windows, link(name = "pythonXY"))]
@ -23,12 +23,12 @@ extern "C" {
#[inline]
pub unsafe fn Py_False() -> *mut PyObject {
&mut _Py_FalseStruct as *mut PyLongObject as *mut PyObject
addr_of_mut_shim!(_Py_FalseStruct) as *mut PyLongObject as *mut PyObject
}
#[inline]
pub unsafe fn Py_True() -> *mut PyObject {
&mut _Py_TrueStruct as *mut PyLongObject as *mut PyObject
addr_of_mut_shim!(_Py_TrueStruct) as *mut PyLongObject as *mut PyObject
}
#[inline]

133
pyo3-ffi/src/buffer.rs Normal file
View file

@ -0,0 +1,133 @@
use crate::object::PyObject;
use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
#[cfg(PyPy)]
const Py_MAX_NDIMS: usize = 36;
#[repr(C)]
#[derive(Copy, Clone)]
pub struct Py_buffer {
pub buf: *mut c_void,
/// Owned reference
pub obj: *mut crate::PyObject,
pub len: Py_ssize_t,
pub itemsize: Py_ssize_t,
pub readonly: c_int,
pub ndim: c_int,
pub format: *mut c_char,
pub shape: *mut Py_ssize_t,
pub strides: *mut Py_ssize_t,
pub suboffsets: *mut Py_ssize_t,
pub internal: *mut c_void,
#[cfg(PyPy)]
pub flags: c_int,
#[cfg(PyPy)]
pub _strides: [Py_ssize_t; Py_MAX_NDIMS],
#[cfg(PyPy)]
pub _shape: [Py_ssize_t; Py_MAX_NDIMS],
}
impl Py_buffer {
pub const fn new() -> Self {
Py_buffer {
buf: ptr::null_mut(),
obj: ptr::null_mut(),
len: 0,
itemsize: 0,
readonly: 0,
ndim: 0,
format: ptr::null_mut(),
shape: ptr::null_mut(),
strides: ptr::null_mut(),
suboffsets: ptr::null_mut(),
internal: ptr::null_mut(),
#[cfg(PyPy)]
flags: 0,
#[cfg(PyPy)]
_strides: [0; Py_MAX_NDIMS],
#[cfg(PyPy)]
_shape: [0; Py_MAX_NDIMS],
}
}
}
/* Return 1 if the getbuffer function is available, otherwise return 0. */
extern "C" {
#[cfg(not(PyPy))]
pub fn PyObject_CheckBuffer(obj: *mut PyObject) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyObject_GetBuffer")]
pub fn PyObject_GetBuffer(obj: *mut PyObject, view: *mut Py_buffer, flags: c_int) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_GetPointer")]
pub fn PyBuffer_GetPointer(view: *const Py_buffer, indices: *const Py_ssize_t) -> *mut c_void;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_SizeFromFormat")]
pub fn PyBuffer_SizeFromFormat(format: *const c_char) -> Py_ssize_t;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_ToContiguous")]
pub fn PyBuffer_ToContiguous(
buf: *mut c_void,
view: *const Py_buffer,
len: Py_ssize_t,
order: c_char,
) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_FromContiguous")]
pub fn PyBuffer_FromContiguous(
view: *const Py_buffer,
buf: *const c_void,
len: Py_ssize_t,
order: c_char,
) -> c_int;
pub fn PyObject_CopyData(dest: *mut PyObject, src: *mut PyObject) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_IsContiguous")]
pub fn PyBuffer_IsContiguous(view: *const Py_buffer, fort: c_char) -> c_int;
pub fn PyBuffer_FillContiguousStrides(
ndims: c_int,
shape: *mut Py_ssize_t,
strides: *mut Py_ssize_t,
itemsize: c_int,
fort: c_char,
);
#[cfg_attr(PyPy, link_name = "PyPyBuffer_FillInfo")]
pub fn PyBuffer_FillInfo(
view: *mut Py_buffer,
o: *mut PyObject,
buf: *mut c_void,
len: Py_ssize_t,
readonly: c_int,
flags: c_int,
) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_Release")]
pub fn PyBuffer_Release(view: *mut Py_buffer);
}
/// Maximum number of dimensions
pub const PyBUF_MAX_NDIM: c_int = 64;
/* Flags for getting buffers */
pub const PyBUF_SIMPLE: c_int = 0;
pub const PyBUF_WRITABLE: c_int = 0x0001;
/* we used to include an E, backwards compatible alias */
pub const PyBUF_WRITEABLE: c_int = PyBUF_WRITABLE;
pub const PyBUF_FORMAT: c_int = 0x0004;
pub const PyBUF_ND: c_int = 0x0008;
pub const PyBUF_STRIDES: c_int = 0x0010 | PyBUF_ND;
pub const PyBUF_C_CONTIGUOUS: c_int = 0x0020 | PyBUF_STRIDES;
pub const PyBUF_F_CONTIGUOUS: c_int = 0x0040 | PyBUF_STRIDES;
pub const PyBUF_ANY_CONTIGUOUS: c_int = 0x0080 | PyBUF_STRIDES;
pub const PyBUF_INDIRECT: c_int = 0x0100 | PyBUF_STRIDES;
pub const PyBUF_CONTIG: c_int = PyBUF_ND | PyBUF_WRITABLE;
pub const PyBUF_CONTIG_RO: c_int = PyBUF_ND;
pub const PyBUF_STRIDED: c_int = PyBUF_STRIDES | PyBUF_WRITABLE;
pub const PyBUF_STRIDED_RO: c_int = PyBUF_STRIDES;
pub const PyBUF_RECORDS: c_int = PyBUF_STRIDES | PyBUF_WRITABLE | PyBUF_FORMAT;
pub const PyBUF_RECORDS_RO: c_int = PyBUF_STRIDES | PyBUF_FORMAT;
pub const PyBUF_FULL: c_int = PyBUF_INDIRECT | PyBUF_WRITABLE | PyBUF_FORMAT;
pub const PyBUF_FULL_RO: c_int = PyBUF_INDIRECT | PyBUF_FORMAT;
pub const PyBUF_READ: c_int = 0x100;
pub const PyBUF_WRITE: c_int = 0x200;

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int};
#[cfg_attr(windows, link(name = "pythonXY"))]
@ -12,12 +12,12 @@ extern "C" {
#[inline]
pub unsafe fn PyByteArray_Check(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, &mut PyByteArray_Type)
PyObject_TypeCheck(op, addr_of_mut_shim!(PyByteArray_Type))
}
#[inline]
pub unsafe fn PyByteArray_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyByteArray_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyByteArray_Type)) as c_int
}
extern "C" {

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int};
#[cfg_attr(windows, link(name = "pythonXY"))]
@ -16,7 +16,7 @@ pub unsafe fn PyBytes_Check(op: *mut PyObject) -> c_int {
#[inline]
pub unsafe fn PyBytes_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyBytes_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyBytes_Type)) as c_int
}
extern "C" {

View file

@ -1,5 +1,5 @@
use crate::ffi::object::PyObject;
use crate::ffi::pystate::PyThreadState;
use crate::object::PyObject;
use crate::pystate::PyThreadState;
use std::os::raw::{c_char, c_int, c_void};
extern "C" {
@ -37,7 +37,7 @@ extern "C" {
pub fn PyEval_GetGlobals() -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyEval_GetLocals")]
pub fn PyEval_GetLocals() -> *mut PyObject;
pub fn PyEval_GetFrame() -> *mut crate::ffi::PyFrameObject;
pub fn PyEval_GetFrame() -> *mut crate::PyFrameObject;
#[cfg_attr(PyPy, link_name = "PyPy_AddPendingCall")]
pub fn Py_AddPendingCall(
func: Option<extern "C" fn(arg1: *mut c_void) -> c_int>,
@ -59,8 +59,8 @@ extern "C" {
pub fn PyEval_GetFuncName(arg1: *mut PyObject) -> *const c_char;
pub fn PyEval_GetFuncDesc(arg1: *mut PyObject) -> *const c_char;
pub fn PyEval_GetCallStats(arg1: *mut PyObject) -> *mut PyObject;
pub fn PyEval_EvalFrame(arg1: *mut crate::ffi::PyFrameObject) -> *mut PyObject;
pub fn PyEval_EvalFrameEx(f: *mut crate::ffi::PyFrameObject, exc: c_int) -> *mut PyObject;
pub fn PyEval_EvalFrame(arg1: *mut crate::PyFrameObject) -> *mut PyObject;
pub fn PyEval_EvalFrameEx(f: *mut crate::PyFrameObject, exc: c_int) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyEval_SaveThread")]
pub fn PyEval_SaveThread() -> *mut PyThreadState;
#[cfg_attr(PyPy, link_name = "PyPyEval_RestoreThread")]

View file

@ -1,4 +1,4 @@
use crate::ffi::object::PyObject;
use crate::object::PyObject;
use std::os::raw::{c_char, c_int};
extern "C" {

View file

@ -1,4 +1,4 @@
use crate::ffi::object::*;
use crate::object::*;
use std::os::raw::{c_double, c_int};
#[repr(C)]
@ -40,12 +40,12 @@ extern "C" {
#[inline]
pub unsafe fn PyComplex_Check(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, &mut PyComplex_Type)
PyObject_TypeCheck(op, addr_of_mut_shim!(PyComplex_Type))
}
#[inline]
pub unsafe fn PyComplex_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyComplex_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyComplex_Type)) as c_int
}
extern "C" {

View file

@ -1,4 +1,4 @@
use crate::ffi::object::{PyObject, PyTypeObject, Py_TYPE};
use crate::object::{PyObject, PyTypeObject, Py_TYPE};
use std::os::raw::{c_char, c_int};
extern "C" {
@ -12,17 +12,17 @@ extern "C" {
#[inline]
pub unsafe fn PyContext_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyContext_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyContext_Type)) as c_int
}
#[inline]
pub unsafe fn PyContextVar_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyContextVar_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyContextVar_Type)) as c_int
}
#[inline]
pub unsafe fn PyContextToken_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyContextToken_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyContextToken_Type)) as c_int
}
extern "C" {

View file

@ -1,8 +1,11 @@
use crate::ffi::{PyObject, Py_buffer, Py_ssize_t};
use std::os::raw::{c_char, c_int, c_void};
use crate::{PyObject, Py_ssize_t};
use std::os::raw::{c_char, c_int};
#[cfg(not(Py_3_11))]
use crate::Py_buffer;
#[cfg(all(Py_3_8, not(PyPy)))]
use crate::ffi::{
use crate::{
pyport::PY_SSIZE_T_MAX, vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET,
PyTuple_Check, PyType_HasFeature, Py_TPFLAGS_HAVE_VECTORCALL,
};
@ -51,7 +54,7 @@ pub unsafe fn PyVectorcall_NARGS(n: size_t) -> Py_ssize_t {
#[inline(always)]
pub unsafe fn PyVectorcall_Function(callable: *mut PyObject) -> Option<vectorcallfunc> {
assert!(!callable.is_null());
let tp = crate::ffi::Py_TYPE(callable);
let tp = crate::Py_TYPE(callable);
if PyType_HasFeature(tp, Py_TPFLAGS_HAVE_VECTORCALL) == 0 {
return None;
}
@ -211,6 +214,7 @@ extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyObject_LengthHint")]
pub fn PyObject_LengthHint(o: *mut PyObject, arg1: Py_ssize_t) -> Py_ssize_t;
#[cfg(not(Py_3_11))] // moved to src/buffer.rs from 3.11
#[cfg(all(Py_3_9, not(PyPy)))]
pub fn PyObject_CheckBuffer(obj: *mut PyObject) -> c_int;
}
@ -218,20 +222,24 @@ extern "C" {
#[cfg(not(any(Py_3_9, PyPy)))]
#[inline]
pub unsafe fn PyObject_CheckBuffer(o: *mut PyObject) -> c_int {
let tp_as_buffer = (*crate::ffi::Py_TYPE(o)).tp_as_buffer;
let tp_as_buffer = (*crate::Py_TYPE(o)).tp_as_buffer;
(!tp_as_buffer.is_null() && (*tp_as_buffer).bf_getbuffer.is_some()) as c_int
}
#[cfg(not(Py_3_11))] // moved to src/buffer.rs from 3.11
extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyObject_GetBuffer")]
pub fn PyObject_GetBuffer(obj: *mut PyObject, view: *mut Py_buffer, flags: c_int) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_GetPointer")]
pub fn PyBuffer_GetPointer(view: *mut Py_buffer, indices: *mut Py_ssize_t) -> *mut c_void;
pub fn PyBuffer_GetPointer(
view: *mut Py_buffer,
indices: *mut Py_ssize_t,
) -> *mut std::os::raw::c_void;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_SizeFromFormat")]
pub fn PyBuffer_SizeFromFormat(format: *const c_char) -> Py_ssize_t;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_ToContiguous")]
pub fn PyBuffer_ToContiguous(
buf: *mut c_void,
buf: *mut std::os::raw::c_void,
view: *mut Py_buffer,
len: Py_ssize_t,
order: c_char,
@ -239,7 +247,7 @@ extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyBuffer_FromContiguous")]
pub fn PyBuffer_FromContiguous(
view: *mut Py_buffer,
buf: *mut c_void,
buf: *mut std::os::raw::c_void,
len: Py_ssize_t,
order: c_char,
) -> c_int;
@ -257,7 +265,7 @@ extern "C" {
pub fn PyBuffer_FillInfo(
view: *mut Py_buffer,
o: *mut PyObject,
buf: *mut c_void,
buf: *mut std::os::raw::c_void,
len: Py_ssize_t,
readonly: c_int,
flags: c_int,

View file

@ -1,5 +1,5 @@
use crate::ffi::object::PyObject;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::PyObject;
use crate::pyport::Py_ssize_t;
use std::os::raw::c_int;
extern "C" {

View file

@ -1,12 +1,9 @@
use crate::ffi::cpython::pystate::Py_tracefunc;
use crate::ffi::object::{freefunc, PyObject};
use crate::cpython::pystate::Py_tracefunc;
use crate::object::{freefunc, PyObject};
use std::os::raw::c_int;
extern "C" {
pub fn _PyEval_EvalFrameDefault(
arg1: *mut crate::ffi::PyFrameObject,
exc: c_int,
) -> *mut PyObject;
pub fn _PyEval_EvalFrameDefault(arg1: *mut crate::PyFrameObject, exc: c_int) -> *mut PyObject;
pub fn _PyEval_RequestCodeExtraIndex(func: freefunc) -> c_int;
pub fn PyEval_SetProfile(trace_func: Option<Py_tracefunc>, arg1: *mut PyObject);
pub fn PyEval_SetTrace(trace_func: Option<Py_tracefunc>, arg1: *mut PyObject);

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int, c_uchar, c_void};
// skipped _Py_CODEUNIT
@ -84,13 +84,13 @@ extern "C" {
#[inline]
#[cfg(not(PyPy))]
pub unsafe fn PyCode_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyCode_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyCode_Type)) as c_int
}
#[inline]
#[cfg(not(PyPy))]
pub unsafe fn PyCode_GetNumFree(op: *mut PyCodeObject) -> Py_ssize_t {
crate::ffi::PyTuple_GET_SIZE((*op).co_freevars)
crate::PyTuple_GET_SIZE((*op).co_freevars)
}
extern "C" {

View file

@ -1,11 +1,11 @@
#[cfg(not(Py_3_10))]
use crate::ffi::object::PyObject;
use crate::object::PyObject;
#[cfg(not(Py_3_10))]
use crate::ffi::pyarena::*;
use crate::pyarena::*;
#[cfg(not(Py_3_10))]
use crate::ffi::pythonrun::*;
use crate::pythonrun::*;
#[cfg(not(Py_3_10))]
use crate::ffi::PyCodeObject;
use crate::PyCodeObject;
#[cfg(not(Py_3_10))]
use std::os::raw::c_char;
use std::os::raw::c_int;

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use std::os::raw::c_int;
opaque_struct!(PyDictKeysObject);
@ -24,7 +24,7 @@ extern "C" {
mp: *mut PyObject,
key: *mut PyObject,
item: *mut PyObject,
hash: crate::ffi::Py_hash_t,
hash: crate::Py_hash_t,
) -> c_int;
// skipped _PyDict_DelItem_KnownHash
// skipped _PyDict_DelItemIf
@ -34,7 +34,7 @@ extern "C" {
pos: *mut Py_ssize_t,
key: *mut *mut PyObject,
value: *mut *mut PyObject,
hash: *mut crate::ffi::Py_hash_t,
hash: *mut crate::Py_hash_t,
) -> c_int;
// skipped PyDict_GET_SIZE
// skipped _PyDict_Contains_KnownHash

View file

@ -1,6 +1,6 @@
use crate::ffi::cpython::code::{PyCodeObject, CO_MAXBLOCKS};
use crate::ffi::object::*;
use crate::ffi::pystate::PyThreadState;
use crate::cpython::code::{PyCodeObject, CO_MAXBLOCKS};
use crate::object::*;
use crate::pystate::PyThreadState;
use std::os::raw::{c_char, c_int};
// skipped _framestate
@ -60,7 +60,7 @@ extern "C" {
#[inline]
pub unsafe fn PyFrame_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyFrame_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyFrame_Type)) as c_int
}
extern "C" {

View file

@ -1,4 +1,4 @@
use crate::ffi::{PyInterpreterState, PyObject};
use crate::{PyInterpreterState, PyObject};
use std::os::raw::{c_char, c_int, c_uchar};
// skipped PyInit__imp

View file

@ -1,6 +1,6 @@
/* --- PyStatus ----------------------------------------------- */
use crate::ffi::Py_ssize_t;
use crate::Py_ssize_t;
use libc::wchar_t;
use std::os::raw::{c_char, c_int, c_ulong};

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
#[repr(C)]
#[derive(Copy, Clone)]

View file

@ -1,4 +1,4 @@
use crate::ffi::PyObject;
use crate::PyObject;
use std::os::raw::c_int;
// skipped _Py_NewReference
@ -11,8 +11,9 @@ use std::os::raw::c_int;
// skipped _Py_static_string
// skipped _Py_IDENTIFIER
#[cfg(not(Py_3_11))] // moved to src/buffer.rs from Python
mod bufferinfo {
use crate::ffi::Py_ssize_t;
use crate::Py_ssize_t;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
@ -24,7 +25,7 @@ mod bufferinfo {
pub struct Py_buffer {
pub buf: *mut c_void,
/// Owned reference
pub obj: *mut crate::ffi::PyObject,
pub obj: *mut crate::PyObject,
pub len: Py_ssize_t,
pub itemsize: Py_ssize_t,
pub readonly: c_int,
@ -67,12 +68,12 @@ mod bufferinfo {
}
pub type getbufferproc = unsafe extern "C" fn(
arg1: *mut crate::ffi::PyObject,
arg1: *mut crate::PyObject,
arg2: *mut Py_buffer,
arg3: c_int,
) -> c_int;
pub type releasebufferproc =
unsafe extern "C" fn(arg1: *mut crate::ffi::PyObject, arg2: *mut Py_buffer);
unsafe extern "C" fn(arg1: *mut crate::PyObject, arg2: *mut Py_buffer);
/// Maximum number of dimensions
pub const PyBUF_MAX_NDIM: c_int = 64;
@ -106,6 +107,7 @@ mod bufferinfo {
pub const PyBUF_WRITE: c_int = 0x200;
}
#[cfg(not(Py_3_11))]
pub use self::bufferinfo::*;
#[cfg(Py_3_8)]
@ -117,8 +119,8 @@ pub type vectorcallfunc = unsafe extern "C" fn(
) -> *mut PyObject;
mod typeobject {
use crate::ffi::{self, object};
use crate::ffi::{PyObject, Py_ssize_t};
use crate::object;
use crate::{PyObject, Py_ssize_t};
use std::mem;
use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void};
@ -199,8 +201,8 @@ mod typeobject {
#[repr(C)]
#[derive(Clone, Default)]
pub struct PyBufferProcs {
pub bf_getbuffer: Option<super::getbufferproc>,
pub bf_releasebuffer: Option<super::releasebufferproc>,
pub bf_getbuffer: Option<crate::getbufferproc>,
pub bf_releasebuffer: Option<crate::releasebufferproc>,
}
pub type printfunc =
@ -248,9 +250,9 @@ mod typeobject {
pub tp_weaklistoffset: Py_ssize_t,
pub tp_iter: Option<object::getiterfunc>,
pub tp_iternext: Option<object::iternextfunc>,
pub tp_methods: *mut ffi::methodobject::PyMethodDef,
pub tp_members: *mut ffi::structmember::PyMemberDef,
pub tp_getset: *mut ffi::descrobject::PyGetSetDef,
pub tp_methods: *mut crate::methodobject::PyMethodDef,
pub tp_members: *mut crate::structmember::PyMemberDef,
pub tp_getset: *mut crate::descrobject::PyGetSetDef,
pub tp_base: *mut PyTypeObject,
pub tp_dict: *mut object::PyObject,
pub tp_descr_get: Option<object::descrgetfunc>,
@ -310,10 +312,10 @@ mod typeobject {
#[inline]
pub unsafe fn PyHeapType_GET_MEMBERS(
etype: *mut PyHeapTypeObject,
) -> *mut ffi::structmember::PyMemberDef {
) -> *mut crate::structmember::PyMemberDef {
let py_type = object::Py_TYPE(etype as *mut object::PyObject);
let ptr = etype.offset((*py_type).tp_basicsize);
ptr as *mut ffi::structmember::PyMemberDef
ptr as *mut crate::structmember::PyMemberDef
}
}

View file

@ -1,4 +1,4 @@
use crate::ffi::{PyConfig, PyPreConfig, PyStatus, Py_ssize_t};
use crate::{PyConfig, PyPreConfig, PyStatus, Py_ssize_t};
use libc::wchar_t;
use std::os::raw::{c_char, c_int};

View file

@ -1,6 +1,6 @@
#[cfg(not(PyPy))]
use crate::ffi::PyThreadState;
use crate::ffi::{PyFrameObject, PyInterpreterState, PyObject};
use crate::PyThreadState;
use crate::{PyFrameObject, PyInterpreterState, PyObject};
use std::os::raw::c_int;
// skipped _PyInterpreterState_RequiresIDRef
@ -60,10 +60,10 @@ extern "C" {
#[cfg(Py_3_9)]
pub type _PyFrameEvalFunction = extern "C" fn(
*mut crate::ffi::PyThreadState,
*mut crate::ffi::PyFrameObject,
*mut crate::PyThreadState,
*mut crate::PyFrameObject,
c_int,
) -> *mut crate::ffi::object::PyObject;
) -> *mut crate::object::PyObject;
#[cfg(Py_3_9)]
extern "C" {

View file

@ -1,9 +1,9 @@
use crate::ffi::object::*;
use crate::object::*;
#[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
use crate::ffi::pyarena::PyArena;
use crate::ffi::PyCompilerFlags;
use crate::pyarena::PyArena;
use crate::PyCompilerFlags;
#[cfg(not(Py_3_10))]
use crate::ffi::{_mod, _node};
use crate::{_mod, _node};
use libc::FILE;
use std::os::raw::{c_char, c_int};

View file

@ -1,6 +1,6 @@
use crate::ffi::object::*;
use crate::object::*;
#[cfg(not(PyPy))]
use crate::ffi::pyport::Py_ssize_t;
use crate::pyport::Py_ssize_t;
#[repr(C)]
pub struct PyTupleObject {

View file

@ -1,4 +1,4 @@
use crate::ffi::{PyObject, Py_UCS1, Py_UCS2, Py_UCS4, Py_UNICODE, Py_hash_t, Py_ssize_t};
use crate::{PyObject, Py_UCS1, Py_UCS2, Py_UCS4, Py_UNICODE, Py_hash_t, Py_ssize_t};
use libc::wchar_t;
use std::os::raw::{c_char, c_int, c_uint, c_void};
@ -119,7 +119,7 @@ pub const SSTATE_INTERNED_IMMORTAL: c_uint = 2;
#[inline]
#[cfg(target_endian = "little")]
pub unsafe fn PyUnicode_IS_ASCII(op: *mut PyObject) -> c_uint {
debug_assert!(crate::ffi::PyUnicode_Check(op) != 0);
debug_assert!(crate::PyUnicode_Check(op) != 0);
debug_assert!(PyUnicode_IS_READY(op) != 0);
(*(op as *mut PyASCIIObject)).ascii()
@ -170,7 +170,7 @@ pub unsafe fn PyUnicode_4BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS4 {
#[inline]
#[cfg(target_endian = "little")]
pub unsafe fn PyUnicode_KIND(op: *mut PyObject) -> c_uint {
debug_assert!(crate::ffi::PyUnicode_Check(op) != 0);
debug_assert!(crate::PyUnicode_Check(op) != 0);
debug_assert!(PyUnicode_IS_READY(op) != 0);
(*(op as *mut PyASCIIObject)).kind()
@ -197,7 +197,7 @@ pub unsafe fn _PyUnicode_NONCOMPACT_DATA(op: *mut PyObject) -> *mut c_void {
#[inline]
#[cfg(target_endian = "little")]
pub unsafe fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void {
debug_assert!(crate::ffi::PyUnicode_Check(op) != 0);
debug_assert!(crate::PyUnicode_Check(op) != 0);
if PyUnicode_IS_COMPACT(op) != 0 {
_PyUnicode_COMPACT_DATA(op)
@ -213,7 +213,7 @@ pub unsafe fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void {
#[inline]
#[cfg(target_endian = "little")]
pub unsafe fn PyUnicode_GET_LENGTH(op: *mut PyObject) -> Py_ssize_t {
debug_assert!(crate::ffi::PyUnicode_Check(op) != 0);
debug_assert!(crate::PyUnicode_Check(op) != 0);
debug_assert!(PyUnicode_IS_READY(op) != 0);
(*(op as *mut PyASCIIObject)).length
@ -230,7 +230,7 @@ pub unsafe fn PyUnicode_IS_READY(op: *mut PyObject) -> c_uint {
#[inline]
#[cfg(target_endian = "little")]
pub unsafe fn PyUnicode_READY(op: *mut PyObject) -> c_int {
debug_assert!(crate::ffi::PyUnicode_Check(op) != 0);
debug_assert!(crate::PyUnicode_Check(op) != 0);
if PyUnicode_IS_READY(op) != 0 {
0
@ -480,130 +480,3 @@ extern "C" {
// skipped _PyUnicode_FromId
// skipped _PyUnicode_EQ
// skipped _PyUnicode_ScanIdentifier
#[cfg(test)]
#[cfg(target_endian = "little")]
mod tests {
use super::*;
use crate::types::PyString;
use crate::{AsPyPointer, Python};
#[test]
fn ascii_object_bitfield() {
let ob_base: PyObject = unsafe { std::mem::zeroed() };
let mut o = PyASCIIObject {
ob_base,
length: 0,
hash: 0,
state: 0,
wstr: std::ptr::null_mut() as *mut wchar_t,
};
unsafe {
assert_eq!(o.interned(), 0);
assert_eq!(o.kind(), 0);
assert_eq!(o.compact(), 0);
assert_eq!(o.ascii(), 0);
assert_eq!(o.ready(), 0);
for i in 0..4 {
o.state = i;
assert_eq!(o.interned(), i);
}
for i in 0..8 {
o.state = i << 2;
assert_eq!(o.kind(), i);
}
o.state = 1 << 5;
assert_eq!(o.compact(), 1);
o.state = 1 << 6;
assert_eq!(o.ascii(), 1);
o.state = 1 << 7;
assert_eq!(o.ready(), 1);
}
}
#[test]
#[cfg_attr(Py_3_10, allow(deprecated))]
fn ascii() {
Python::with_gil(|py| {
// This test relies on implementation details of PyString.
let s = PyString::new(py, "hello, world");
let ptr = s.as_ptr();
unsafe {
let ascii_ptr = ptr as *mut PyASCIIObject;
let ascii = ascii_ptr.as_ref().unwrap();
assert_eq!(ascii.interned(), 0);
assert_eq!(ascii.kind(), PyUnicode_1BYTE_KIND);
assert_eq!(ascii.compact(), 1);
assert_eq!(ascii.ascii(), 1);
assert_eq!(ascii.ready(), 1);
assert_eq!(PyUnicode_IS_ASCII(ptr), 1);
assert_eq!(PyUnicode_IS_COMPACT(ptr), 1);
assert_eq!(PyUnicode_IS_COMPACT_ASCII(ptr), 1);
assert!(!PyUnicode_1BYTE_DATA(ptr).is_null());
// 2 and 4 byte macros return nonsense for this string instance.
assert_eq!(PyUnicode_KIND(ptr), PyUnicode_1BYTE_KIND);
assert!(!_PyUnicode_COMPACT_DATA(ptr).is_null());
// _PyUnicode_NONCOMPACT_DATA isn't valid for compact strings.
assert!(!PyUnicode_DATA(ptr).is_null());
assert_eq!(PyUnicode_GET_LENGTH(ptr), s.len().unwrap() as _);
assert_eq!(PyUnicode_IS_READY(ptr), 1);
// This has potential to mutate object. But it should be a no-op since
// we're already ready.
assert_eq!(PyUnicode_READY(ptr), 0);
}
})
}
#[test]
#[cfg_attr(Py_3_10, allow(deprecated))]
fn ucs4() {
Python::with_gil(|py| {
let s = "哈哈🐈";
let py_string = PyString::new(py, s);
let ptr = py_string.as_ptr();
unsafe {
let ascii_ptr = ptr as *mut PyASCIIObject;
let ascii = ascii_ptr.as_ref().unwrap();
assert_eq!(ascii.interned(), 0);
assert_eq!(ascii.kind(), PyUnicode_4BYTE_KIND);
assert_eq!(ascii.compact(), 1);
assert_eq!(ascii.ascii(), 0);
assert_eq!(ascii.ready(), 1);
assert_eq!(PyUnicode_IS_ASCII(ptr), 0);
assert_eq!(PyUnicode_IS_COMPACT(ptr), 1);
assert_eq!(PyUnicode_IS_COMPACT_ASCII(ptr), 0);
assert!(!PyUnicode_4BYTE_DATA(ptr).is_null());
assert_eq!(PyUnicode_KIND(ptr), PyUnicode_4BYTE_KIND);
assert!(!_PyUnicode_COMPACT_DATA(ptr).is_null());
// _PyUnicode_NONCOMPACT_DATA isn't valid for compact strings.
assert!(!PyUnicode_DATA(ptr).is_null());
assert_eq!(PyUnicode_GET_LENGTH(ptr), py_string.len().unwrap() as _);
assert_eq!(PyUnicode_IS_READY(ptr), 1);
// This has potential to mutate object. But it should be a no-op since
// we're already ready.
assert_eq!(PyUnicode_READY(ptr), 0);
}
})
}
}

View file

@ -9,15 +9,13 @@
//! Support for `PyDateTime_CAPI` is limited as of PyPy 7.0.0.
//! `DateTime_FromTimestamp` and `Date_FromTimestamp` are currently not supported.
use crate::ffi::{PyObject, PyTypeObject};
use crate::ffi::{PyObject_TypeCheck, Py_TYPE};
use crate::once_cell::GILOnceCell;
use crate::Python;
use std::ops::Deref;
use crate::{PyObject, PyObject_TypeCheck, PyTypeObject, Py_TYPE};
use std::cell::UnsafeCell;
use std::os::raw::{c_char, c_int, c_uchar};
use std::ptr;
#[cfg(not(PyPy))]
use {
crate::ffi::{PyCapsule_Import, Py_hash_t},
crate::{PyCapsule_Import, Py_hash_t},
std::ffi::CString,
};
@ -434,62 +432,38 @@ pub struct PyDateTime_CAPI {
// Python already shares this object between threads, so it's no more evil for us to do it too!
unsafe impl Sync for PyDateTime_CAPI {}
/// Safe wrapper around the Python datetime C-API global. Note that this object differs slightly
/// from the equivalent C object: in C, this is implemented as a `static PyDateTime_CAPI *`. Here
/// this is implemented as a wrapper which implements [`Deref`] to access a reference to a
/// [`PyDateTime_CAPI`] object.
/// Returns a pointer to a `PyDateTime_CAPI` instance
///
/// In the [`Deref`] implementation, if the underlying object has not yet been initialized, the GIL
/// will automatically be acquired and [`PyDateTime_IMPORT()`] called.
pub static PyDateTimeAPI: _PyDateTimeAPI_impl = _PyDateTimeAPI_impl {
inner: GILOnceCell::new(),
};
/// # Note
/// This function will return a null pointer until
/// `PyDateTime_IMPORT` is called
#[inline]
pub unsafe fn PyDateTimeAPI() -> *mut PyDateTime_CAPI {
*PyDateTimeAPI_impl.0.get()
}
/// Safe wrapper around the Python C-API global `PyDateTime_TimeZone_UTC`. This follows a similar
/// strategy as [`PyDateTimeAPI`]: the Python datetime C-API will automatically be imported if this
/// type is deferenced.
///
/// The type obtained by dereferencing this object is `&'static PyObject`. This may change in the
/// future to be a more specific type representing that this is a `datetime.timezone` object.
#[cfg(not(all(PyPy, not(Py_3_8))))]
pub static PyDateTime_TimeZone_UTC: _PyDateTime_TimeZone_UTC_impl = _PyDateTime_TimeZone_UTC_impl {
inner: &PyDateTimeAPI,
};
#[inline]
pub unsafe fn PyDateTime_TimeZone_UTC() -> *mut PyObject {
(*PyDateTimeAPI()).TimeZone_UTC
}
/// Populates the `PyDateTimeAPI` object
///
/// Unlike in C, this does *not* need to be actively invoked in Rust, which
/// will populate the `PyDateTimeAPI` struct automatically on first use.
/// Use this function only if you want to eagerly load the datetime module,
/// such as if you do not want the first call to a datetime function to be
/// slightly slower than subsequent calls.
///
/// # Safety
/// The Python GIL must be held.
pub unsafe fn PyDateTime_IMPORT() -> &'static PyDateTime_CAPI {
PyDateTimeAPI
.inner
.get_or_init(Python::assume_gil_acquired(), || {
// Because `get_or_init` is called with `assume_gil_acquired()`, it's necessary to acquire
// the GIL here to prevent segfault in usage of the safe `PyDateTimeAPI::deref` impl.
Python::with_gil(|_py| {
// PyPy expects the C-API to be initialized via PyDateTime_Import, so trying to use
// `PyCapsule_Import` will behave unexpectedly in pypy.
#[cfg(PyPy)]
let py_datetime_c_api = PyDateTime_Import();
pub unsafe fn PyDateTime_IMPORT() {
// PyPy expects the C-API to be initialized via PyDateTime_Import, so trying to use
// `PyCapsule_Import` will behave unexpectedly in pypy.
#[cfg(PyPy)]
let py_datetime_c_api = PyDateTime_Import();
#[cfg(not(PyPy))]
let py_datetime_c_api = {
// PyDateTime_CAPSULE_NAME is a macro in C
let PyDateTime_CAPSULE_NAME = CString::new("datetime.datetime_CAPI").unwrap();
#[cfg(not(PyPy))]
let py_datetime_c_api = {
// PyDateTime_CAPSULE_NAME is a macro in C
let PyDateTime_CAPSULE_NAME = CString::new("datetime.datetime_CAPI").unwrap();
&*(PyCapsule_Import(PyDateTime_CAPSULE_NAME.as_ptr(), 1)
as *const PyDateTime_CAPI)
};
PyCapsule_Import(PyDateTime_CAPSULE_NAME.as_ptr(), 1) as *mut PyDateTime_CAPI
};
py_datetime_c_api
})
})
*PyDateTimeAPI_impl.0.get() = py_datetime_c_api;
}
// skipped non-limited PyDateTime_TimeZone_UTC
@ -502,61 +476,61 @@ pub unsafe fn PyDateTime_IMPORT() -> &'static PyDateTime_CAPI {
#[inline]
/// Check if `op` is a `PyDateTimeAPI.DateType` or subtype.
pub unsafe fn PyDate_Check(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, PyDateTimeAPI.DateType) as c_int
PyObject_TypeCheck(op, (*PyDateTimeAPI()).DateType) as c_int
}
#[inline]
/// Check if `op`'s type is exactly `PyDateTimeAPI.DateType`.
pub unsafe fn PyDate_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == PyDateTimeAPI.DateType) as c_int
(Py_TYPE(op) == (*PyDateTimeAPI()).DateType) as c_int
}
#[inline]
/// Check if `op` is a `PyDateTimeAPI.DateTimeType` or subtype.
pub unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, PyDateTimeAPI.DateTimeType) as c_int
PyObject_TypeCheck(op, (*PyDateTimeAPI()).DateTimeType) as c_int
}
#[inline]
/// Check if `op`'s type is exactly `PyDateTimeAPI.DateTimeType`.
pub unsafe fn PyDateTime_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == PyDateTimeAPI.DateTimeType) as c_int
(Py_TYPE(op) == (*PyDateTimeAPI()).DateTimeType) as c_int
}
#[inline]
/// Check if `op` is a `PyDateTimeAPI.TimeType` or subtype.
pub unsafe fn PyTime_Check(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, PyDateTimeAPI.TimeType) as c_int
PyObject_TypeCheck(op, (*PyDateTimeAPI()).TimeType) as c_int
}
#[inline]
/// Check if `op`'s type is exactly `PyDateTimeAPI.TimeType`.
pub unsafe fn PyTime_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == PyDateTimeAPI.TimeType) as c_int
(Py_TYPE(op) == (*PyDateTimeAPI()).TimeType) as c_int
}
#[inline]
/// Check if `op` is a `PyDateTimeAPI.DetaType` or subtype.
pub unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, PyDateTimeAPI.DeltaType) as c_int
PyObject_TypeCheck(op, (*PyDateTimeAPI()).DeltaType) as c_int
}
#[inline]
/// Check if `op`'s type is exactly `PyDateTimeAPI.DeltaType`.
pub unsafe fn PyDelta_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == PyDateTimeAPI.DeltaType) as c_int
(Py_TYPE(op) == (*PyDateTimeAPI()).DeltaType) as c_int
}
#[inline]
/// Check if `op` is a `PyDateTimeAPI.TZInfoType` or subtype.
pub unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, PyDateTimeAPI.TZInfoType) as c_int
PyObject_TypeCheck(op, (*PyDateTimeAPI()).TZInfoType) as c_int
}
#[inline]
/// Check if `op`'s type is exactly `PyDateTimeAPI.TZInfoType`.
pub unsafe fn PyTZInfo_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == PyDateTimeAPI.TZInfoType) as c_int
(Py_TYPE(op) == (*PyDateTimeAPI()).TZInfoType) as c_int
}
// skipped non-limited PyDate_FromDate
@ -570,12 +544,14 @@ pub unsafe fn PyTZInfo_CheckExact(op: *mut PyObject) -> c_int {
#[cfg(not(PyPy))]
pub unsafe fn PyDateTime_FromTimestamp(args: *mut PyObject) -> *mut PyObject {
(PyDateTimeAPI.DateTime_FromTimestamp)(PyDateTimeAPI.DateTimeType, args, std::ptr::null_mut())
let f = (*PyDateTimeAPI()).DateTime_FromTimestamp;
f((*PyDateTimeAPI()).DateTimeType, args, std::ptr::null_mut())
}
#[cfg(not(PyPy))]
pub unsafe fn PyDate_FromTimestamp(args: *mut PyObject) -> *mut PyObject {
(PyDateTimeAPI.Date_FromTimestamp)(PyDateTimeAPI.DateType, args)
let f = (*PyDateTimeAPI()).Date_FromTimestamp;
f((*PyDateTimeAPI()).DateType, args)
}
#[cfg(PyPy)]
@ -589,96 +565,13 @@ extern "C" {
#[cfg(PyPy)]
extern "C" {
#[link_name = "_PyPyDateTime_Import"]
pub fn PyDateTime_Import() -> &'static PyDateTime_CAPI;
pub fn PyDateTime_Import() -> *mut PyDateTime_CAPI;
}
// -- implementation details which are specific to Rust. --
// Rust specific implementation details
#[doc(hidden)]
pub struct _PyDateTimeAPI_impl {
inner: GILOnceCell<&'static PyDateTime_CAPI>,
}
struct PyDateTimeAPISingleton(UnsafeCell<*mut PyDateTime_CAPI>);
unsafe impl Sync for PyDateTimeAPISingleton {}
impl Deref for _PyDateTimeAPI_impl {
type Target = PyDateTime_CAPI;
#[inline]
fn deref(&self) -> &'static PyDateTime_CAPI {
unsafe { PyDateTime_IMPORT() }
}
}
#[doc(hidden)]
#[cfg(not(all(PyPy, not(Py_3_8))))]
pub struct _PyDateTime_TimeZone_UTC_impl {
inner: &'static _PyDateTimeAPI_impl,
}
#[cfg(not(all(PyPy, not(Py_3_8))))]
impl Deref for _PyDateTime_TimeZone_UTC_impl {
type Target = crate::PyObject;
#[inline]
fn deref(&self) -> &crate::PyObject {
unsafe {
&*((&self.inner.TimeZone_UTC) as *const *mut crate::ffi::PyObject
as *const crate::PyObject)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{types::PyDict, AsPyPointer, IntoPy, Py, PyAny, Python};
#[test]
fn test_datetime_fromtimestamp() {
Python::with_gil(|py| {
let args: Py<PyAny> = (100,).into_py(py);
unsafe { PyDateTime_IMPORT() };
let dt: &PyAny = unsafe { py.from_owned_ptr(PyDateTime_FromTimestamp(args.as_ptr())) };
let locals = PyDict::new(py);
locals.set_item("dt", dt).unwrap();
py.run(
"import datetime; assert dt == datetime.datetime.fromtimestamp(100)",
None,
Some(locals),
)
.unwrap();
})
}
#[test]
fn test_date_fromtimestamp() {
Python::with_gil(|py| {
let args: Py<PyAny> = (100,).into_py(py);
unsafe { PyDateTime_IMPORT() };
let dt: &PyAny = unsafe { py.from_owned_ptr(PyDate_FromTimestamp(args.as_ptr())) };
let locals = PyDict::new(py);
locals.set_item("dt", dt).unwrap();
py.run(
"import datetime; assert dt == datetime.date.fromtimestamp(100)",
None,
Some(locals),
)
.unwrap();
})
}
#[test]
#[cfg(not(all(PyPy, not(Py_3_8))))]
fn test_utc_timezone() {
Python::with_gil(|py| {
let utc_timezone = PyDateTime_TimeZone_UTC.as_ref(py);
let locals = PyDict::new(py);
locals.set_item("utc_timezone", utc_timezone).unwrap();
py.run(
"import datetime; assert utc_timezone is datetime.timezone.utc",
None,
Some(locals),
)
.unwrap();
})
}
}
static PyDateTimeAPI_impl: PyDateTimeAPISingleton =
PyDateTimeAPISingleton(UnsafeCell::new(ptr::null_mut()));

View file

@ -1,6 +1,6 @@
use crate::ffi::methodobject::PyMethodDef;
use crate::ffi::object::{PyObject, PyTypeObject};
use crate::ffi::structmember::PyMemberDef;
use crate::methodobject::PyMethodDef;
use crate::object::{PyObject, PyTypeObject};
use crate::structmember::PyMemberDef;
use std::os::raw::{c_char, c_int, c_void};
pub type getter = unsafe extern "C" fn(slf: *mut PyObject, closure: *mut c_void) -> *mut PyObject;

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int};
#[cfg_attr(windows, link(name = "pythonXY"))]
@ -15,7 +15,7 @@ pub unsafe fn PyDict_Check(op: *mut PyObject) -> c_int {
#[inline]
pub unsafe fn PyDict_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyDict_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyDict_Type)) as c_int
}
extern "C" {
@ -76,17 +76,17 @@ extern "C" {
#[inline]
pub unsafe fn PyDictKeys_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyDictKeys_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyDictKeys_Type)) as c_int
}
#[inline]
pub unsafe fn PyDictValues_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyDictValues_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyDictValues_Type)) as c_int
}
#[inline]
pub unsafe fn PyDictItems_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyDictItems_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyDictItems_Type)) as c_int
}
#[inline]

View file

@ -1,4 +1,4 @@
use crate::ffi::object::PyTypeObject;
use crate::object::PyTypeObject;
#[cfg_attr(windows, link(name = "pythonXY"))]
extern "C" {

View file

@ -1,4 +1,4 @@
use crate::ffi::object::PyObject;
use crate::object::PyObject;
use std::os::raw::c_int;
extern "C" {

View file

@ -1,4 +1,4 @@
use crate::ffi::object::PyObject;
use crate::object::PyObject;
use std::os::raw::{c_char, c_int};
pub const PY_STDIOTEXTMODE: &str = "b";

View file

@ -1,4 +1,4 @@
use crate::ffi::pyport::Py_ssize_t;
use crate::pyport::Py_ssize_t;
use libc::wchar_t;
use std::os::raw::c_char;

View file

@ -1,4 +1,4 @@
use crate::ffi::object::*;
use crate::object::*;
use std::os::raw::{c_double, c_int};
#[cfg(Py_LIMITED_API)]
@ -20,12 +20,12 @@ extern "C" {
#[inline]
pub unsafe fn PyFloat_Check(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, &mut PyFloat_Type)
PyObject_TypeCheck(op, addr_of_mut_shim!(PyFloat_Type))
}
#[inline]
pub unsafe fn PyFloat_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyFloat_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyFloat_Type)) as c_int
}
// skipped Py_RETURN_NAN

View file

@ -1,6 +1,6 @@
use std::os::raw::c_int;
use crate::ffi::object::{PyObject, PyTypeObject, Py_TYPE};
use crate::object::{PyObject, PyTypeObject, Py_TYPE};
// skipped PyFunctionObject
@ -12,7 +12,7 @@ extern "C" {
#[inline]
pub unsafe fn PyFunction_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyFunction_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyFunction_Type)) as c_int
}
extern "C" {

View file

@ -1,6 +1,6 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::ffi::PyFrameObject;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use crate::PyFrameObject;
use std::os::raw::c_int;
#[repr(C)]
@ -25,12 +25,12 @@ extern "C" {
#[inline]
pub unsafe fn PyGen_Check(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, &mut PyGen_Type)
PyObject_TypeCheck(op, addr_of_mut_shim!(PyGen_Type))
}
#[inline]
pub unsafe fn PyGen_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyGen_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyGen_Type)) as c_int
}
extern "C" {
@ -55,7 +55,7 @@ extern "C" {
#[inline]
pub unsafe fn PyCoro_CheckExact(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, &mut PyCoro_Type)
PyObject_TypeCheck(op, addr_of_mut_shim!(PyCoro_Type))
}
// skipped _PyCoro_GetAwaitableIter
@ -75,7 +75,7 @@ extern "C" {
#[inline]
pub unsafe fn PyAsyncGen_CheckExact(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, &mut PyAsyncGen_Type)
PyObject_TypeCheck(op, addr_of_mut_shim!(PyAsyncGen_Type))
}
// skipped _PyAsyncGenValueWrapperNew

View file

@ -1,4 +1,4 @@
use crate::ffi::object::PyObject;
use crate::object::PyObject;
use std::os::raw::{c_char, c_int, c_long};
extern "C" {

View file

@ -1,4 +1,4 @@
use crate::ffi::object::*;
use crate::object::*;
use std::os::raw::c_int;
#[cfg_attr(windows, link(name = "pythonXY"))]
@ -9,7 +9,7 @@ extern "C" {
#[inline]
pub unsafe fn PySeqIter_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PySeqIter_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PySeqIter_Type)) as c_int
}
extern "C" {
@ -19,7 +19,7 @@ extern "C" {
#[inline]
pub unsafe fn PyCallIter_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyCallIter_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyCallIter_Type)) as c_int
}
extern "C" {

447
pyo3-ffi/src/lib.rs Normal file
View file

@ -0,0 +1,447 @@
//! Raw FFI declarations for Python's C API.
//!
//! PyO3 can be used to write native Python modules or run Python code and modules from Rust.
//!
//! This crate just provides low level bindings to the Python interpreter.
//! It is meant for advanced users only - regular PyO3 users shouldn't
//! need to interact with this crate at all.
//!
//! The contents of this crate are not documented here, as it would entail
//! basically copying the documentation from CPython. Consult the [Python/C API Reference
//! Manual][capi] for up-to-date documentation.
//!
//! # Safety
//!
//! The functions in this crate lack individual safety documentation, but
//! generally the following apply:
//! - Pointer arguments have to point to a valid Python object of the correct type,
//! although null pointers are sometimes valid input.
//! - The vast majority can only be used safely while the GIL is held.
//! - Some functions have additional safety requirements, consult the
//! [Python/C API Reference Manual][capi]
//! for more information.
//!
//!
//! # Feature flags
//!
//! PyO3 uses [feature flags] to enable you to opt-in to additional functionality. For a detailed
//! description, see the [Features chapter of the guide].
//!
//! ## Optional feature flags
//!
//! The following features customize PyO3's behavior:
//!
//! - `abi3`: Restricts PyO3's API to a subset of the full Python API which is guaranteed by
//! [PEP 384] to be forward-compatible with future Python versions.
//! - `extension-module`: This will tell the linker to keep the Python symbols unresolved, so that
//! your module can also be used with statically linked Python interpreters. Use this feature when
//! building an extension module.
//!
//! ## `rustc` environment flags
//!
//! PyO3 uses `rustc`'s `--cfg` flags to enable or disable code used for different Python versions.
//! If you want to do this for your own crate, you can do so with the [`pyo3-build-config`] crate.
//!
//! - `Py_3_7`, `Py_3_8`, `Py_3_9`, `Py_3_10`: Marks code that is only enabled when
//! compiling for a given minimum Python version.
//! - `Py_LIMITED_API`: Marks code enabled when the `abi3` feature flag is enabled.
//! - `PyPy` - Marks code enabled when compiling for PyPy.
//!
//! # Minimum supported Rust and Python versions
//!
//! PyO3 supports the following software versions:
//! - Python 3.7 and up (CPython and PyPy)
//! - Rust 1.48 and up
//!
//! # Example: Building Python Native modules
//!
//! PyO3 can be used to generate a native Python module. The easiest way to try this out for the
//! first time is to use [`maturin`]. `maturin` is a tool for building and publishing Rust-based
//! Python packages with minimal configuration. The following steps set up some files for an example
//! Python module, install `maturin`, and then show how to build and import the Python module.
//!
//! First, create a new folder (let's call it `string_sum`) containing the following two files:
//!
//! **`Cargo.toml`**
//!
//! ```toml
//! [lib]
//! name = "string_sum"
//! # "cdylib" is necessary to produce a shared library for Python to import from.
//! #
//! # Downstream Rust code (including code in `bin/`, `examples/`, and `tests/`) will not be able
//! # to `use string_sum;` unless the "rlib" or "lib" crate type is also included, e.g.:
//! # crate-type = ["cdylib", "rlib"]
//! crate-type = ["cdylib"]
//!
//! [dependencies.pyo3-ffi]
// workaround for `extended_key_value_attributes`: https://github.com/rust-lang/rust/issues/82768#issuecomment-803935643
#![cfg_attr(docsrs, cfg_attr(docsrs, doc = concat!("version = \"", env!("CARGO_PKG_VERSION"), "\"")))]
#![cfg_attr(not(docsrs), doc = "version = \"*\"")]
//! features = ["extension-module"]
//! ```
//!
//! **`src/lib.rs`**
//! ```rust
//! use std::os::raw::c_char;
//! use std::ptr;
//!
//! use pyo3_ffi::*;
//!
//! #[allow(non_snake_case)]
//! #[no_mangle]
//! pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject {
//! let init = PyModuleDef {
//! m_base: PyModuleDef_HEAD_INIT,
//! m_name: "string_sum\0".as_ptr() as *const c_char,
//! m_doc: std::ptr::null(),
//! m_size: 0,
//! m_methods: std::ptr::null_mut(),
//! m_slots: std::ptr::null_mut(),
//! m_traverse: None,
//! m_clear: None,
//! m_free: None,
//! };
//!
//! let mptr = PyModule_Create(Box::into_raw(Box::new(init)));
//! let version = env!("CARGO_PKG_VERSION");
//! PyModule_AddObject(
//! mptr,
//! "__version__\0".as_ptr() as *const c_char,
//! PyUnicode_FromStringAndSize(version.as_ptr() as *const c_char, version.len() as isize),
//! );
//!
//! let wrapped_sum_as_string = PyMethodDef {
//! ml_name: "sum_as_string\0".as_ptr() as *const c_char,
//! 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() as *const c_char,
//! };
//!
//! // PyModule_AddObject can technically fail.
//! // For more involved applications error checking may be necessary
//! PyModule_AddObject(
//! mptr,
//! "sum_as_string\0".as_ptr() as *const c_char,
//! PyCFunction_NewEx(
//! Box::into_raw(Box::new(wrapped_sum_as_string)),
//! std::ptr::null_mut(),
//! PyUnicode_InternFromString("string_sum\0".as_ptr() as *const c_char),
//! ),
//! );
//!
//! let all = ["__all__\0", "__version__\0", "sum_as_string\0"];
//!
//! let pyall = PyTuple_New(all.len() as isize);
//! for (i, obj) in all.iter().enumerate() {
//! PyTuple_SET_ITEM(
//! pyall,
//! i as isize,
//! PyUnicode_InternFromString(obj.as_ptr() as *const c_char),
//! )
//! }
//!
//! PyModule_AddObject(mptr, "__all__\0".as_ptr() as *const c_char, pyall);
//!
//! mptr
//! }
//!
//! pub unsafe extern "C" fn sum_as_string(
//! _self: *mut PyObject,
//! args: *mut *mut PyObject,
//! nargs: Py_ssize_t,
//! ) -> *mut PyObject {
//! if nargs != 2 {
//! return raise_type_error("sum_as_string() expected 2 positional arguments");
//! }
//!
//! let arg1 = *args;
//! if PyLong_Check(arg1) == 0 {
//! return raise_type_error("sum_as_string() expected an int for positional argument 1");
//! }
//!
//! let arg1 = PyLong_AsLong(arg1);
//! if !PyErr_Occurred().is_null() {
//! return ptr::null_mut()
//! }
//!
//! let arg2 = *args.add(1);
//! if PyLong_Check(arg2) == 0 {
//! return raise_type_error("sum_as_string() expected an int for positional argument 2");
//! }
//!
//! let arg2 = PyLong_AsLong(arg2);
//! if !PyErr_Occurred().is_null() {
//! return ptr::null_mut()
//! }
//! let res = (arg1 + arg2).to_string();
//! PyUnicode_FromStringAndSize(res.as_ptr() as *const c_char, res.len() as isize)
//! }
//!
//! #[cold]
//! #[inline(never)]
//! fn raise_type_error(msg: &str) -> *mut PyObject {
//! unsafe {
//! let err_msg =
//! PyUnicode_FromStringAndSize(msg.as_ptr() as *const c_char, msg.len() as isize);
//! PyErr_SetObject(PyExc_TypeError, err_msg);
//! Py_DECREF(err_msg);
//! };
//! std::ptr::null_mut()
//! }
//! ```
//!
//! With those two files in place, now `maturin` needs to be installed. This can be done using
//! Python's package manager `pip`. First, load up a new Python `virtualenv`, and install `maturin`
//! into it:
//! ```bash
//! $ cd string_sum
//! $ python -m venv .env
//! $ source .env/bin/activate
//! $ pip install maturin
//! ```
//!
//! Now build and execute the module:
//! ```bash
//! $ maturin develop
//! # lots of progress output as maturin runs the compilation...
//! $ python
//! >>> import string_sum
//! >>> string_sum.sum_as_string(5, 20)
//! '25'
//! ```
//!
//! As well as with `maturin`, it is possible to build using [setuptools-rust] or
//! [manually][manual_builds]. Both offer more flexibility than `maturin` but require further
//! configuration.
//!
//!
//! # Using Python from Rust
//!
//! To embed Python into a Rust binary, you need to ensure that your Python installation contains a
//! shared library. The following steps demonstrate how to ensure this (for Ubuntu).
//!
//! To install the Python shared library on Ubuntu:
//! ```bash
//! sudo apt install python3-dev
//! ```
//!
//! While most projects use the safe wrapper provided by pyo3,
//! you can take a look at the [`orjson`] library as an example on how to use `pyo3-ffi` directly.
//! For those well versed in C and Rust the [tutorials] from the CPython documentation
//! can be easily converted to rust as well.
//!
//! [tutorials]: https://docs.python.org/3/extending/
//! [`orjson`]: https://github.com/ijl/orjson
//! [capi]: https://docs.python.org/3/c-api/index.html
//! [`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"
//! [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"
#![allow(
missing_docs,
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
clippy::upper_case_acronyms,
clippy::missing_safety_doc
)]
// Until `extern type` is stabilized, use the recommended approach to
// model opaque types:
// https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs
macro_rules! opaque_struct {
($name:ident) => {
#[repr(C)]
pub struct $name([u8; 0]);
};
}
macro_rules! addr_of_mut_shim {
($place:expr) => {{
#[cfg(addr_of)]
{
::std::ptr::addr_of_mut!($place)
}
#[cfg(not(addr_of))]
{
&mut $place as *mut _
}
}};
}
pub use self::abstract_::*;
pub use self::bltinmodule::*;
pub use self::boolobject::*;
#[cfg(Py_3_11)]
pub use self::buffer::*;
pub use self::bytearrayobject::*;
pub use self::bytesobject::*;
pub use self::ceval::*;
pub use self::code::*;
pub use self::codecs::*;
pub use self::compile::*;
pub use self::complexobject::*;
#[cfg(all(Py_3_8, not(Py_LIMITED_API)))]
pub use self::context::*;
#[cfg(not(Py_LIMITED_API))]
pub use self::datetime::*;
pub use self::descrobject::*;
pub use self::dictobject::*;
pub use self::enumobject::*;
pub use self::eval::*;
pub use self::fileobject::*;
pub use self::fileutils::*;
pub use self::floatobject::*;
#[cfg(not(Py_LIMITED_API))]
pub use self::funcobject::*;
#[cfg(not(Py_LIMITED_API))]
pub use self::genobject::*;
pub use self::import::*;
pub use self::intrcheck::*;
pub use self::iterobject::*;
pub use self::listobject::*;
pub use self::longobject::*;
pub use self::marshal::*;
pub use self::memoryobject::*;
pub use self::methodobject::*;
pub use self::modsupport::*;
pub use self::moduleobject::*;
pub use self::object::*;
pub use self::objimpl::*;
pub use self::osmodule::*;
#[cfg(not(Py_LIMITED_API))]
pub use self::pyarena::*;
pub use self::pycapsule::*;
pub use self::pyerrors::*;
pub use self::pyframe::*;
pub use self::pyhash::*;
pub use self::pylifecycle::*;
pub use self::pymem::*;
pub use self::pyport::*;
pub use self::pystate::*;
pub use self::pystrtod::*;
pub use self::pythonrun::*;
pub use self::rangeobject::*;
pub use self::setobject::*;
pub use self::sliceobject::*;
pub use self::structseq::*;
pub use self::sysmodule::*;
pub use self::traceback::*;
pub use self::tupleobject::*;
pub use self::typeslots::*;
pub use self::unicodeobject::*;
pub use self::warnings::*;
pub use self::weakrefobject::*;
mod abstract_;
// skipped asdl.h
// skipped ast.h
mod bltinmodule;
mod boolobject;
#[cfg(Py_3_11)]
mod buffer;
mod bytearrayobject;
mod bytesobject;
// skipped cellobject.h
mod ceval;
// skipped classobject.h
mod code;
mod codecs;
mod compile;
mod complexobject;
#[cfg(all(Py_3_8, not(Py_LIMITED_API)))]
mod context; // It's actually 3.7.1, but no cfg for patches.
#[cfg(not(Py_LIMITED_API))]
pub(crate) mod datetime;
mod descrobject;
mod dictobject;
// skipped dynamic_annotations.h
mod enumobject;
// skipped errcode.h
mod eval;
// skipped exports.h
mod fileobject;
mod fileutils;
mod floatobject;
// skipped empty frameobject.h
#[cfg(not(Py_LIMITED_API))]
pub(crate) mod funcobject;
// skipped genericaliasobject.h
#[cfg(not(Py_LIMITED_API))]
mod genobject;
mod import;
// skipped interpreteridobject.h
mod intrcheck;
mod iterobject;
mod listobject;
// skipped longintrepr.h
mod longobject;
pub(crate) mod marshal;
mod memoryobject;
mod methodobject;
mod modsupport;
mod moduleobject;
// skipped namespaceobject.h
mod object;
mod objimpl;
// skipped odictobject.h
// skipped opcode.h
// skipped osdefs.h
mod osmodule;
// skipped parser_interface.h
// skipped patchlevel.h
// skipped picklebufobject.h
// skipped pyctype.h
// skipped py_curses.h
#[cfg(not(Py_LIMITED_API))]
mod pyarena;
mod pycapsule;
// skipped pydecimal.h
// skipped pydtrace.h
mod pyerrors;
// skipped pyexpat.h
// skipped pyfpe.h
mod pyframe;
mod pyhash;
mod pylifecycle;
// skipped pymacconfig.h
// skipped pymacro.h
// skipped pymath.h
mod pymem;
mod pyport;
mod pystate;
mod pythonrun;
// skipped pystrhex.h
// skipped pystrcmp.h
mod pystrtod;
// skipped pythread.h
// skipped pytime.h
mod rangeobject;
mod setobject;
mod sliceobject;
mod structseq;
mod sysmodule;
mod traceback;
// skipped tracemalloc.h
mod tupleobject;
mod typeslots;
mod unicodeobject;
mod warnings;
mod weakrefobject;
// Additional headers that are not exported by Python.h
pub mod structmember;
// "Limited API" definitions matching Python's `include/cpython` directory.
#[cfg(not(Py_LIMITED_API))]
mod cpython;
#[cfg(not(Py_LIMITED_API))]
pub use self::cpython::*;

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use std::os::raw::c_int;
#[cfg_attr(windows, link(name = "pythonXY"))]
@ -17,7 +17,7 @@ pub unsafe fn PyList_Check(op: *mut PyObject) -> c_int {
#[inline]
pub unsafe fn PyList_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyList_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyList_Type)) as c_int
}
extern "C" {

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use libc::size_t;
#[cfg(not(Py_LIMITED_API))]
use std::os::raw::c_uchar;
@ -20,7 +20,7 @@ pub unsafe fn PyLong_Check(op: *mut PyObject) -> c_int {
#[inline]
pub unsafe fn PyLong_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyLong_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyLong_Type)) as c_int
}
extern "C" {
@ -96,8 +96,8 @@ extern "C" {
extern "C" {
// skipped non-limited _PyLong_Sign
#[cfg(not(PyPy))]
pub fn _PyLong_NumBits(obj: *mut PyObject) -> c_int;
#[cfg_attr(PyPy, link_name = "_PyPyLong_NumBits")]
pub fn _PyLong_NumBits(obj: *mut PyObject) -> size_t;
// skipped _PyLong_DivmodNear
@ -109,7 +109,7 @@ extern "C" {
is_signed: c_int,
) -> *mut PyObject;
#[cfg(not(PyPy))]
#[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")]
pub fn _PyLong_AsByteArray(
v: *mut PyLongObject,
bytes: *mut c_uchar,

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int};
// skipped non-limited _PyManagedBuffer_Type
@ -12,7 +12,7 @@ extern "C" {
#[inline]
pub unsafe fn PyMemoryView_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyMemoryView_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyMemoryView_Type)) as c_int
}
// skipped non-limited PyMemoryView_GET_BUFFER

View file

@ -1,6 +1,6 @@
use crate::ffi::object::{PyObject, PyTypeObject, Py_TYPE};
use crate::object::{PyObject, PyTypeObject, Py_TYPE};
#[cfg(Py_3_9)]
use crate::ffi::PyObject_TypeCheck;
use crate::PyObject_TypeCheck;
use std::mem;
use std::os::raw::{c_char, c_int};
@ -13,30 +13,29 @@ extern "C" {
#[cfg(Py_3_9)]
#[inline]
pub unsafe fn PyCFunction_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyCFunction_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyCFunction_Type)) as c_int
}
#[cfg(Py_3_9)]
#[inline]
pub unsafe fn PyCFunction_Check(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, &mut PyCFunction_Type)
PyObject_TypeCheck(op, addr_of_mut_shim!(PyCFunction_Type))
}
#[cfg(not(Py_3_9))]
#[inline]
pub unsafe fn PyCFunction_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyCFunction_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyCFunction_Type)) as c_int
}
pub type PyCFunction =
unsafe extern "C" fn(slf: *mut PyObject, args: *mut PyObject) -> *mut PyObject;
#[cfg(not(Py_LIMITED_API))]
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
pub type _PyCFunctionFast = unsafe extern "C" fn(
slf: *mut PyObject,
args: *mut *mut PyObject,
nargs: crate::ffi::pyport::Py_ssize_t,
kwnames: *mut PyObject,
nargs: crate::pyport::Py_ssize_t,
) -> *mut PyObject;
pub type PyCFunctionWithKeywords = unsafe extern "C" fn(
@ -49,11 +48,18 @@ pub type PyCFunctionWithKeywords = unsafe extern "C" fn(
pub type _PyCFunctionFastWithKeywords = unsafe extern "C" fn(
slf: *mut PyObject,
args: *const *mut PyObject,
nargs: crate::ffi::pyport::Py_ssize_t,
nargs: crate::pyport::Py_ssize_t,
kwnames: *mut PyObject,
) -> *mut PyObject;
// skipped PyCMethod (since 3.9)
#[cfg(all(Py_3_9, not(Py_LIMITED_API)))]
pub type PyCMethod = unsafe extern "C" fn(
slf: *mut PyObject,
defining_class: *mut PyTypeObject,
args: *const *mut PyObject,
nargs: crate::pyport::Py_ssize_t,
kwnames: *mut PyObject,
) -> *mut PyObject;
extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyCFunction_GetFunction")]
@ -72,17 +78,45 @@ extern "C" {
#[derive(Copy, Clone)]
pub struct PyMethodDef {
pub ml_name: *const c_char,
pub ml_meth: Option<PyCFunction>,
pub ml_meth: PyMethodDefPointer,
pub ml_flags: c_int,
pub ml_doc: *const c_char,
}
impl Default for PyMethodDef {
fn default() -> PyMethodDef {
unsafe { mem::zeroed() }
}
/// Function types used to implement Python callables.
///
/// This function pointer must be accompanied by the correct [ml_flags](PyMethodDef::ml_flags),
/// otherwise the behavior is undefined.
///
/// See the [Python C API documentation][1] for more information.
///
/// [1]: https://docs.python.org/3/c-api/structures.html#implementing-functions-and-methods
#[repr(C)]
#[derive(Copy, Clone)]
pub union PyMethodDefPointer {
/// This variant corresponds with [`METH_VARARGS`] *or* [`METH_NOARGS`] *or* [`METH_O`].
pub PyCFunction: PyCFunction,
/// This variant corresponds with [`METH_VARARGS`] | [`METH_KEYWORDS`].
pub PyCFunctionWithKeywords: PyCFunctionWithKeywords,
/// This variant corresponds with [`METH_FASTCALL`].
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
pub _PyCFunctionFast: _PyCFunctionFast,
/// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`].
#[cfg(not(Py_LIMITED_API))]
pub _PyCFunctionFastWithKeywords: _PyCFunctionFastWithKeywords,
/// This variant corresponds with [`METH_METHOD`] | [`METH_FASTCALL`] | [`METH_KEYWORDS`].
#[cfg(all(Py_3_9, not(Py_LIMITED_API)))]
pub PyCMethod: PyCMethod,
}
// TODO: This can be a const assert on Rust 1.57
const _: () =
[()][mem::size_of::<PyMethodDefPointer>() - mem::size_of::<Option<extern "C" fn()>>()];
extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyCFunction_New")]
pub fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject;
@ -123,7 +157,9 @@ be specified alone or with METH_KEYWORDS. */
pub const METH_FASTCALL: c_int = 0x0080;
// skipped METH_STACKLESS
// skipped METH_METHOD
#[cfg(all(Py_3_9, not(Py_LIMITED_API)))]
pub const METH_METHOD: c_int = 0x0200;
extern "C" {
#[cfg(not(Py_3_9))]

View file

@ -1,7 +1,7 @@
use crate::ffi::methodobject::PyMethodDef;
use crate::ffi::moduleobject::PyModuleDef;
use crate::ffi::object::PyObject;
use crate::ffi::pyport::Py_ssize_t;
use crate::methodobject::PyMethodDef;
use crate::moduleobject::PyModuleDef;
use crate::object::PyObject;
use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int, c_long};
extern "C" {

View file

@ -1,6 +1,6 @@
use crate::ffi::methodobject::PyMethodDef;
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::methodobject::PyMethodDef;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int, c_void};
#[cfg_attr(windows, link(name = "pythonXY"))]
@ -11,12 +11,12 @@ extern "C" {
#[inline]
pub unsafe fn PyModule_Check(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, &mut PyModule_Type)
PyObject_TypeCheck(op, addr_of_mut_shim!(PyModule_Type))
}
#[inline]
pub unsafe fn PyModule_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyModule_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyModule_Type)) as c_int
}
extern "C" {

View file

@ -1,6 +1,6 @@
// FFI note: this file changed a lot between 3.6 and 3.10.
// Some missing definitions may not be marked "skipped".
use crate::ffi::pyport::{Py_hash_t, Py_ssize_t};
use crate::pyport::{Py_hash_t, Py_ssize_t};
use std::mem;
use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void};
use std::ptr;
@ -9,7 +9,7 @@ use std::ptr;
opaque_struct!(PyTypeObject);
#[cfg(not(Py_LIMITED_API))]
pub use crate::ffi::cpython::object::PyTypeObject;
pub use crate::cpython::object::PyTypeObject;
// _PyObject_HEAD_EXTRA: conditionally defined in PyObject_HEAD_INIT
// _PyObject_EXTRA_INIT: conditionally defined in PyObject_HEAD_INIT
@ -162,6 +162,11 @@ pub type newfunc = unsafe extern "C" fn(
) -> *mut PyObject;
pub type allocfunc =
unsafe extern "C" fn(arg1: *mut PyTypeObject, arg2: Py_ssize_t) -> *mut PyObject;
#[cfg(Py_3_11)]
pub type getbufferproc =
unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut crate::Py_buffer, arg3: c_int) -> c_int;
#[cfg(Py_3_11)]
pub type releasebufferproc = unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut crate::Py_buffer);
#[repr(C)]
#[derive(Copy, Clone)]
@ -407,7 +412,7 @@ pub unsafe fn Py_DECREF(op: *mut PyObject) {
}
#[inline]
pub unsafe fn Py_CLEAR(op: &mut *mut PyObject) {
pub unsafe fn Py_CLEAR(op: *mut *mut PyObject) {
let tmp = *op;
if !tmp.is_null() {
*op = ptr::null_mut();
@ -465,7 +470,7 @@ extern "C" {
#[inline]
pub unsafe fn Py_None() -> *mut PyObject {
&mut _Py_NoneStruct
addr_of_mut_shim!(_Py_NoneStruct)
}
#[inline]
@ -483,7 +488,7 @@ extern "C" {
#[inline]
pub unsafe fn Py_NotImplemented() -> *mut PyObject {
&mut _Py_NotImplementedStruct
addr_of_mut_shim!(_Py_NotImplementedStruct)
}
// skipped Py_RETURN_NOTIMPLEMENTED
@ -531,5 +536,5 @@ pub unsafe fn PyType_Check(op: *mut PyObject) -> c_int {
#[inline]
pub unsafe fn PyType_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyType_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyType_Type)) as c_int
}

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use libc::size_t;
use std::os::raw::{c_int, c_void};

View file

@ -1,4 +1,4 @@
use crate::ffi::object::PyObject;
use crate::object::PyObject;
extern "C" {
pub fn PyOS_FSPath(path: *mut PyObject) -> *mut PyObject;

View file

@ -1,4 +1,4 @@
use crate::ffi::object::*;
use crate::object::*;
use std::os::raw::{c_char, c_int, c_void};
#[cfg_attr(windows, link(name = "pythonXY"))]
@ -11,7 +11,7 @@ pub type PyCapsule_Destructor = unsafe extern "C" fn(o: *mut PyObject);
#[inline]
pub unsafe fn PyCapsule_CheckExact(ob: *mut PyObject) -> c_int {
(Py_TYPE(ob) == &mut PyCapsule_Type) as c_int
(Py_TYPE(ob) == addr_of_mut_shim!(PyCapsule_Type)) as c_int
}
extern "C" {

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int};
#[repr(C)]
@ -158,7 +158,7 @@ pub unsafe fn PyUnicodeDecodeError_Create(
end: Py_ssize_t,
_reason: *const c_char,
) -> *mut PyObject {
crate::ffi::PyObject_CallFunction(
crate::PyObject_CallFunction(
PyExc_UnicodeDecodeError,
std::ffi::CStr::from_bytes_with_nul(b"sy#nns\0")
.unwrap()

View file

@ -1,5 +1,5 @@
#[cfg(not(Py_LIMITED_API))]
use crate::ffi::PyFrameObject;
use crate::PyFrameObject;
use std::os::raw::c_int;
#[cfg(Py_LIMITED_API)]

View file

@ -1,5 +1,5 @@
#[cfg(not(Py_LIMITED_API))]
use crate::ffi::pyport::{Py_hash_t, Py_ssize_t};
use crate::pyport::{Py_hash_t, Py_ssize_t};
#[cfg(not(Py_LIMITED_API))]
use std::os::raw::{c_char, c_void};

View file

@ -1,4 +1,4 @@
use crate::ffi::pystate::PyThreadState;
use crate::pystate::PyThreadState;
use libc::wchar_t;
use std::os::raw::{c_char, c_int};

View file

@ -1,6 +1,6 @@
#[cfg(not(PyPy))]
use crate::ffi::moduleobject::PyModuleDef;
use crate::ffi::object::PyObject;
use crate::moduleobject::PyModuleDef;
use crate::object::PyObject;
use std::os::raw::c_int;
#[cfg(not(PyPy))]

View file

@ -1,4 +1,4 @@
use crate::ffi::object::PyObject;
use crate::object::PyObject;
use std::os::raw::{c_char, c_double, c_int};
extern "C" {

View file

@ -1,4 +1,4 @@
use crate::ffi::object::*;
use crate::object::*;
#[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
use libc::FILE;
#[cfg(any(Py_LIMITED_API, not(Py_3_10)))]
@ -47,7 +47,7 @@ opaque_struct!(_node);
#[inline]
pub unsafe fn PyParser_SimpleParseString(s: *const c_char, b: c_int) -> *mut _node {
#[allow(deprecated)]
crate::ffi::PyParser_SimpleParseStringFlags(s, b, 0)
crate::PyParser_SimpleParseStringFlags(s, b, 0)
}
#[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
@ -55,7 +55,7 @@ pub unsafe fn PyParser_SimpleParseString(s: *const c_char, b: c_int) -> *mut _no
#[inline]
pub unsafe fn PyParser_SimpleParseFile(fp: *mut FILE, s: *const c_char, b: c_int) -> *mut _node {
#[allow(deprecated)]
crate::ffi::PyParser_SimpleParseFileFlags(fp, s, b, 0)
crate::PyParser_SimpleParseFileFlags(fp, s, b, 0)
}
extern "C" {

View file

@ -1,4 +1,4 @@
use crate::ffi::object::*;
use crate::object::*;
use std::os::raw::c_int;
#[cfg_attr(windows, link(name = "pythonXY"))]
@ -11,5 +11,5 @@ extern "C" {
#[inline]
pub unsafe fn PyRange_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyRange_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyRange_Type)) as c_int
}

View file

@ -1,7 +1,7 @@
use crate::ffi::object::*;
use crate::object::*;
#[cfg(not(Py_LIMITED_API))]
use crate::ffi::pyport::Py_hash_t;
use crate::ffi::pyport::Py_ssize_t;
use crate::pyport::Py_hash_t;
use crate::pyport::Py_ssize_t;
use std::os::raw::c_int;
pub const PySet_MINSIZE: usize = 8;
@ -82,7 +82,7 @@ extern "C" {
#[inline]
#[cfg(not(PyPy))]
pub unsafe fn PyFrozenSet_CheckExact(ob: *mut PyObject) -> c_int {
(Py_TYPE(ob) == &mut PyFrozenSet_Type) as c_int
(Py_TYPE(ob) == addr_of_mut_shim!(PyFrozenSet_Type)) as c_int
}
extern "C" {
@ -94,8 +94,8 @@ extern "C" {
#[inline]
#[cfg(not(PyPy))]
pub unsafe fn PyFrozenSet_Check(ob: *mut PyObject) -> c_int {
(Py_TYPE(ob) == &mut PyFrozenSet_Type
|| PyType_IsSubtype(Py_TYPE(ob), &mut PyFrozenSet_Type) != 0) as c_int
(Py_TYPE(ob) == addr_of_mut_shim!(PyFrozenSet_Type)
|| PyType_IsSubtype(Py_TYPE(ob), addr_of_mut_shim!(PyFrozenSet_Type)) != 0) as c_int
}
extern "C" {
@ -107,20 +107,21 @@ extern "C" {
#[inline]
#[cfg(not(PyPy))]
pub unsafe fn PyAnySet_CheckExact(ob: *mut PyObject) -> c_int {
(Py_TYPE(ob) == &mut PySet_Type || Py_TYPE(ob) == &mut PyFrozenSet_Type) as c_int
(Py_TYPE(ob) == addr_of_mut_shim!(PySet_Type)
|| Py_TYPE(ob) == addr_of_mut_shim!(PyFrozenSet_Type)) as c_int
}
#[inline]
pub unsafe fn PyAnySet_Check(ob: *mut PyObject) -> c_int {
(PyAnySet_CheckExact(ob) != 0
|| PyType_IsSubtype(Py_TYPE(ob), &mut PySet_Type) != 0
|| PyType_IsSubtype(Py_TYPE(ob), &mut PyFrozenSet_Type) != 0) as c_int
|| PyType_IsSubtype(Py_TYPE(ob), addr_of_mut_shim!(PySet_Type)) != 0
|| PyType_IsSubtype(Py_TYPE(ob), addr_of_mut_shim!(PyFrozenSet_Type)) != 0) as c_int
}
#[inline]
#[cfg(Py_3_10)]
pub unsafe fn PySet_CheckExact(op: *mut PyObject) -> c_int {
crate::ffi::Py_IS_TYPE(op, &mut PySet_Type)
crate::Py_IS_TYPE(op, addr_of_mut_shim!(PySet_Type))
}
extern "C" {
@ -132,5 +133,6 @@ extern "C" {
#[inline]
#[cfg(not(PyPy))]
pub unsafe fn PySet_Check(ob: *mut PyObject) -> c_int {
(Py_TYPE(ob) == &mut PySet_Type || PyType_IsSubtype(Py_TYPE(ob), &mut PySet_Type) != 0) as c_int
(Py_TYPE(ob) == addr_of_mut_shim!(PySet_Type)
|| PyType_IsSubtype(Py_TYPE(ob), addr_of_mut_shim!(PySet_Type)) != 0) as c_int
}

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use std::os::raw::c_int;
extern "C" {
@ -9,7 +9,7 @@ extern "C" {
#[inline]
pub unsafe fn Py_Ellipsis() -> *mut PyObject {
&mut _Py_EllipsisObject
addr_of_mut_shim!(_Py_EllipsisObject)
}
#[cfg(not(Py_LIMITED_API))]
@ -30,7 +30,7 @@ extern "C" {
#[inline]
pub unsafe fn PySlice_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PySlice_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PySlice_Type)) as c_int
}
extern "C" {

View file

@ -1,5 +1,5 @@
use crate::ffi::object::PyObject;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::PyObject;
use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int};
#[repr(C)]

View file

@ -1,6 +1,6 @@
use crate::ffi::object::{PyObject, PyTypeObject};
use crate::object::{PyObject, PyTypeObject};
#[cfg(not(PyPy))]
use crate::ffi::pyport::Py_ssize_t;
use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int};
#[repr(C)]
@ -40,18 +40,18 @@ extern "C" {
}
#[cfg(not(Py_LIMITED_API))]
pub type PyStructSequence = crate::ffi::PyTupleObject;
pub type PyStructSequence = crate::PyTupleObject;
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
#[inline]
pub unsafe fn PyStructSequence_SET_ITEM(op: *mut PyObject, i: Py_ssize_t, v: *mut PyObject) {
crate::ffi::PyTuple_SET_ITEM(op, i, v)
crate::PyTuple_SET_ITEM(op, i, v)
}
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
#[inline]
pub unsafe fn PyStructSequence_GET_ITEM(op: *mut PyObject, i: Py_ssize_t) -> *mut PyObject {
crate::ffi::PyTuple_GET_ITEM(op, i)
crate::PyTuple_GET_ITEM(op, i)
}
extern "C" {

View file

@ -1,4 +1,4 @@
use crate::ffi::object::PyObject;
use crate::object::PyObject;
use libc::wchar_t;
use std::os::raw::{c_char, c_int};

View file

@ -1,9 +1,9 @@
use crate::ffi::object::*;
use crate::object::*;
use std::os::raw::c_int;
extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyTraceBack_Here")]
pub fn PyTraceBack_Here(arg1: *mut crate::ffi::PyFrameObject) -> c_int;
pub fn PyTraceBack_Here(arg1: *mut crate::PyFrameObject) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyTraceBack_Print")]
pub fn PyTraceBack_Print(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int;
}
@ -21,5 +21,5 @@ extern "C" {
#[inline]
#[cfg(not(PyPy))]
pub unsafe fn PyTraceBack_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyTraceBack_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyTraceBack_Type)) as c_int
}

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use std::os::raw::c_int;
#[cfg_attr(windows, link(name = "pythonXY"))]
@ -16,7 +16,7 @@ pub unsafe fn PyTuple_Check(op: *mut PyObject) -> c_int {
#[inline]
pub unsafe fn PyTuple_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyTuple_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyTuple_Type)) as c_int
}
extern "C" {

View file

@ -1,8 +1,6 @@
use std::os::raw::c_int;
#[cfg(not(Py_LIMITED_API))]
pub const Py_bf_getbuffer: c_int = 1;
#[cfg(not(Py_LIMITED_API))]
pub const Py_bf_releasebuffer: c_int = 2;
pub const Py_mp_ass_subscript: c_int = 3;
pub const Py_mp_length: c_int = 4;

View file

@ -1,5 +1,5 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use crate::object::*;
use crate::pyport::Py_ssize_t;
use libc::wchar_t;
use std::os::raw::{c_char, c_int, c_void};
@ -34,7 +34,7 @@ pub unsafe fn PyUnicode_Check(op: *mut PyObject) -> c_int {
#[inline]
#[cfg(not(PyPy))]
pub unsafe fn PyUnicode_CheckExact(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyUnicode_Type) as c_int
(Py_TYPE(op) == addr_of_mut_shim!(PyUnicode_Type)) as c_int
}
pub const Py_UNICODE_REPLACEMENT_CHARACTER: Py_UCS4 = 0xFFFD;

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