Merge branch 'master' into parse_args_msg

This commit is contained in:
kngwyu 2019-08-31 17:02:55 +09:00
commit 96b71bfb76
79 changed files with 1046 additions and 775 deletions

View file

@ -5,8 +5,6 @@ dist: xenial
cache:
pip: true
cargo: true
directories:
- "$HOME/kcov"
matrix:
include:
@ -20,10 +18,10 @@ matrix:
python: "3.8-dev"
- name: Minimum nightly
python: "3.7"
# Keep this synced up with build.rs
env: TRAVIS_RUST_VERSION=nightly-2019-02-07
# Tested via anaconda PyPy (since travis's PyPy version is too old)
- name: PyPy3.5 7.0
# Keep this synced up with build.rs and ensure that the nightly version does have clippy available
# https://static.rust-lang.org/dist/YYYY-MM-DD/clippy-nightly-x86_64-unknown-linux-gnu.tar.gz exists
env: TRAVIS_RUST_VERSION=nightly-2019-07-19
- name: PyPy3.5 7.0 # Tested via anaconda PyPy (since travis's PyPy version is too old)
python: "3.7"
env: FEATURES="pypy" PATH="$PATH:/opt/anaconda/envs/pypy3/bin"
allow_failures:
@ -47,11 +45,15 @@ addons:
before_install:
- source ./ci/travis/setup.sh
- curl -L https://github.com/mozilla/grcov/releases/latest/download/grcov-linux-x86_64.tar.bz2 | tar jxf -
install:
- pip install setuptools-rust pytest pytest-benchmark tox tox-venv
- pip install setuptools-rust pytest pytest-benchmark tox
script:
# Options for grcov
- if ! [[ $FEATURES == *"pypy"* ]]; then export CARGO_INCREMENTAL=0; fi
- if ! [[ $FEATURES == *"pypy"* ]]; then export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Zno-landing-pads"; fi
- ./ci/travis/test.sh
after_success:

View file

@ -5,12 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.8.0]
### Added
* `module` argument to `pyclass` macro. [#499](https://github.com/PyO3/pyo3/pull/499)
* `py_run!` macro [#512](https://github.com/PyO3/pyo3/pull/512)
* Use existing fields and methods before calling custom __getattr__. [#505](https://github.com/PyO3/pyo3/pull/512)
* `PyBytes` can now be indexed just like `Vec<u8>`
* Implement `IntoPy<PyObject>` for `PyRef` and `PyRefMut`.
## Removed
* `IntoPyObject` was replaced with `IntoPy<PyObject>`
* `#[pyclass(subclass)]` is hidden a `unsound-subclass` feature because it's causing segmentation faults.
### Fixed
@ -18,6 +26,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Changed
* Implementing the Using the `gc` parameter for `pyclass` (e.g. `#[pyclass(gc)]`) without implementing the `class::PyGCProtocol` trait is now a compile-time error. Failing to implement this trait could lead to segfaults. [#532](https://github.com/PyO3/pyo3/pull/532)
* `PyByteArray::data` has been replaced with `PyDataArray::to_vec` because returning a `&[u8]` is unsound. (See [this comment](https://github.com/PyO3/pyo3/issues/373#issuecomment-512332696) for a great write-up for why that was unsound)
* Replace `mashup` with `paste`.
## [0.7.0] - 2018-05-26
### Added

View file

@ -19,25 +19,25 @@ travis-ci = { repository = "PyO3/pyo3", branch = "master" }
appveyor = { repository = "fafhrd91/pyo3" }
[dependencies]
libc = "0.2.54"
spin = "0.5.0"
num-traits = "0.2.6"
libc = "0.2.62"
spin = "0.5.1"
num-traits = "0.2.8"
pyo3cls = { path = "pyo3cls", version = "=0.7.0" }
mashup = "0.1.9"
num-complex = { version = "0.2.1", optional = true }
inventory = "0.1.3"
indoc = "0.3.3"
unindent = "0.1.3"
num-complex = { version = "0.2.3", optional = true }
inventory = "0.1.4"
indoc = "0.3.4"
unindent = "0.1.4"
paste = "0.1.6"
[dev-dependencies]
assert_approx_eq = "1.1.0"
trybuild = "1.0"
trybuild = "1.0.11"
[build-dependencies]
regex = "1.1.6"
version_check = "0.1.5"
serde = { version = "1.0.91", features = ["derive"] }
serde_json = "1.0.39"
regex = "1.2.1"
version_check = "0.9.1"
serde = { version = "1.0.99", features = ["derive"] }
serde_json = "1.0.40"
[features]
default = []
@ -55,6 +55,9 @@ extension-module = []
# are welcome.
# abi3 = []
# Activate subclassing support
unsound-subclass = ["pyo3cls/unsound-subclass"]
[workspace]
members = [
"pyo3cls",

View file

@ -1,8 +1,12 @@
.PHONY: test test_py3 publish
.PHONY: test test_py3 publish clippy lint fmt
# Constants used in clippy target
CLIPPY_LINTS_TO_DENY := warnings
CLIPPY_LINTS_TO_ALLOW := clippy::new_ret_no_self
test:
cargo test
cargo clippy
${MAKE} clippy
tox
for example in examples/*; do tox -e py -c $$example/tox.ini; done
@ -10,6 +14,18 @@ test_py3:
tox -e py3
for example in examples/*; do tox -e py3 -c $$example/tox.ini; done
fmt:
cargo fmt --all -- --check
clippy:
@touch src/lib.rs # Touching file to ensure that cargo clippy will re-check the project
cargo clippy --all-features --all-targets -- \
$(addprefix -D ,${CLIPPY_LINTS_TO_DENY}) \
$(addprefix -A ,${CLIPPY_LINTS_TO_ALLOW})
lint: fmt clippy
@true
publish: test
cargo publish --manifest-path pyo3-derive-backend/Cargo.toml
cargo publish --manifest-path pyo3cls/Cargo.toml

View file

@ -16,7 +16,7 @@ A comparison with rust-cpython can be found [in the guide](https://pyo3.rs/maste
## Usage
PyO3 supports Python 3.5 and up. The minimum required Rust version is 1.34.0-nightly 2019-02-06.
PyO3 supports Python 3.5 and up. The minimum required Rust version is 1.37.0-nightly 2019-07-19.
PyPy is also supported (via cpyext) for Python 3.5 only, targeted PyPy version is 7.0.0.
Please refer to the guide for installation instruction against PyPy.
@ -129,6 +129,7 @@ fn main() -> PyResult<()> {
* Contains an example of building wheels on Azure Pipelines
* [fastuuid](https://github.com/thedrow/fastuuid/) _Python bindings to Rust's UUID library_
* [python-ext-wasm](https://github.com/wasmerio/python-ext-wasm) _Python library to run WebAssembly binaries_
* [dict-derive](https://github.com/gperinazzo/dict-derive) _Derive FromPyObject to automatically transform Python dicts into Rust structs_
## License

View file

@ -9,7 +9,7 @@ use test::Bencher;
fn iter_dict(b: &mut Bencher) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 1_000_00;
const LEN: usize = 100_000;
let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py);
let mut sum = 0;
b.iter(|| {

View file

@ -11,13 +11,13 @@ use std::path::Path;
use std::process::exit;
use std::process::Command;
use std::process::Stdio;
use version_check::{is_min_date, is_min_version, supports_features};
use version_check::{Channel, Date, Version};
/// Specifies the minimum nightly version needed to compile pyo3.
/// Keep this synced up with the travis ci config,
/// But note that this is the rustc version which can be lower than the nightly version
const MIN_DATE: &'static str = "2019-02-06";
const MIN_VERSION: &'static str = "1.34.0-nightly";
const MIN_DATE: &'static str = "2019-07-18";
const MIN_VERSION: &'static str = "1.37.0-nightly";
/// Information returned from python interpreter
#[derive(Deserialize, Debug)]
@ -537,42 +537,25 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<(String), String>
}
fn check_rustc_version() {
let ok_channel = supports_features();
let ok_version = is_min_version(MIN_VERSION);
let ok_date = is_min_date(MIN_DATE);
let channel = Channel::read().expect("Failed to determine rustc channel");
if !channel.supports_features() {
panic!("Error: pyo3 requires a nightly or dev version of Rust.");
}
let print_version_err = |version: &str, date: &str| {
eprintln!(
"Installed version is: {} ({}). Minimum required: {} ({}).",
version, date, MIN_VERSION, MIN_DATE
);
};
let actual_version = Version::read().expect("Failed to determine the rustc version");
if !actual_version.at_least(MIN_VERSION) {
panic!(
"pyo3 requires at least rustc {}, while the current version is {}",
MIN_VERSION, actual_version
)
}
match (ok_channel, ok_version, ok_date) {
(Some(ok_channel), Some((ok_version, version)), Some((ok_date, date))) => {
if !ok_channel {
eprintln!("Error: pyo3 requires a nightly or dev version of Rust.");
print_version_err(&*version, &*date);
panic!("Aborting compilation due to incompatible compiler.")
}
if !ok_version || !ok_date {
eprintln!("Error: pyo3 requires a more recent version of rustc.");
eprintln!("Use `rustup update` or your preferred method to update Rust");
print_version_err(&*version, &*date);
panic!("Aborting compilation due to incompatible compiler.")
}
}
_ => {
println!(
"cargo:warning={}",
"pyo3 was unable to check rustc compatibility."
);
println!(
"cargo:warning={}",
"Build may fail due to incompatible rustc version."
);
}
let actual_date = Date::read().expect("Failed to determine the rustc date");
if !actual_date.at_least(MIN_DATE) {
panic!(
"pyo3 requires at least rustc {}, while the current rustc date is {}",
MIN_DATE, actual_date
)
}
}

View file

@ -8,27 +8,9 @@ if [[ $FEATURES == *"pypy"* ]]; then
exit 0
fi
### Run kcov ###################################################################
### Run grcov ##################################################################
rm -f target/debug/pyo3*.d
rm -f target/debug/test_*.d
rm -f target/debug/test_doc-*
zip -0 ccov.zip `find . \( -name "pyo3*.gc*" \) -print`;
./grcov ccov.zip -s . -t lcov --llvm --branch --ignore-not-existing --ignore-dir "/*" -o lcov.info;
bash <(curl -s https://codecov.io/bash) -f lcov.info;
# Note: On travis this is run with -P1 because it started failing with
# `-P $(nproc)`. kcov can probably be run in parallel if used with different CI
FILES=$(find . -path ./target/debug/pyo3\* -or -path ./target/debug/test_\*)
echo $FILES | xargs -n1 -P1 sh -c '
dir="target/cov/$(basename $@)"
mkdir -p $dir
echo "Collecting coverage data of $(basename $@)"
kcov \
--exclude-path=./tests \
--exclude-region="#[cfg(test)]:#[cfg(testkcovstopmarker)]" \
--exclude-pattern=/.cargo,/usr/lib \
--verify $dir "$@" 2>&1 >/dev/null
' _
### Upload coverage ############################################################
echo "Uploading code coverage"
curl -SsL https://codecov.io/bash | bash

View file

@ -37,18 +37,3 @@ PYTHON_LIB=$($PYTHON_BINARY -c "import sysconfig; print(sysconfig.get_config_var
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$PYTHON_LIB:$HOME/rust/lib"
echo ${LD_LIBRARY_PATH}
### Setup kcov #################################################################
if [ ! -f "$HOME/.cargo/bin/kcov" ]; then
if [ ! -d "$HOME/kcov/.git" ]; then
git clone --depth=1 https://github.com/SimonKagstrom/kcov \
--branch=v36 "$HOME/kcov"
fi
cd $HOME/kcov
cmake .
make
install src/kcov $HOME/.cargo/bin/kcov
cd $TRAVIS_BUILD_DIR
fi

View file

@ -11,8 +11,7 @@ else
fi
if [ "$TRAVIS_JOB_NAME" = "Minimum nightly" ]; then
cargo fmt --all -- --check
cargo clippy --features "$FEATURES num-complex"
make lint
fi
for example_dir in examples/*; do

2
codecov.yml Normal file
View file

@ -0,0 +1,2 @@
comment: off

View file

@ -9,7 +9,7 @@ edition = "2018"
[dependencies.pyo3]
path = "../../"
features = ["extension-module"]
features = ["extension-module", "unsound-subclass"]
[lib]
name = "rustapi_module"

View file

@ -4,7 +4,7 @@ import platform
import pytest
import rustapi_module.datetime as rdt
from hypothesis import given
from hypothesis import given, example
from hypothesis import strategies as st
from hypothesis.strategies import dates, datetimes
@ -34,13 +34,8 @@ UTC = _get_utc()
MAX_SECONDS = int(pdt.timedelta.max.total_seconds())
MIN_SECONDS = int(pdt.timedelta.min.total_seconds())
try:
MAX_DAYS = pdt.timedelta.max // pdt.timedelta(days=1)
MIN_DAYS = pdt.timedelta.min // pdt.timedelta(days=1)
except Exception:
# Python 2 compatibility
MAX_DAYS = MAX_SECONDS // pdt.timedelta(days=1).total_seconds()
MIN_DAYS = MIN_SECONDS // pdt.timedelta(days=1).total_seconds()
MAX_DAYS = pdt.timedelta.max // pdt.timedelta(days=1)
MIN_DAYS = pdt.timedelta.min // pdt.timedelta(days=1)
MAX_MICROSECONDS = int(pdt.timedelta.max.total_seconds() * 1e6)
MIN_MICROSECONDS = int(pdt.timedelta.min.total_seconds() * 1e6)
@ -48,11 +43,10 @@ MIN_MICROSECONDS = int(pdt.timedelta.min.total_seconds() * 1e6)
IS_X86 = platform.architecture()[0] == '32bit'
IS_WINDOWS = sys.platform == 'win32'
if IS_WINDOWS:
MIN_DATETIME_FROM_TIMESTAMP = pdt.datetime.fromtimestamp(86400)
if IS_X86:
MIN_DATETIME_FROM_TIMESTAMP = pdt.datetime.fromtimestamp(86400)
MAX_DATETIME_FROM_TIMESTAMP = pdt.datetime.fromtimestamp(32536789199)
else:
MIN_DATETIME_FROM_TIMESTAMP = pdt.datetime.fromtimestamp(0)
MAX_DATETIME_FROM_TIMESTAMP = pdt.datetime.fromtimestamp(32536799999)
else:
if IS_X86:
@ -65,14 +59,6 @@ else:
PYPY = platform.python_implementation() == "PyPy"
HAS_FOLD = getattr(pdt.datetime, "fold", False)
# Helper functions
get_timestamp = getattr(pdt.datetime, "timestamp", None)
if get_timestamp is None:
def get_timestamp(dt):
# Python 2 compatibility
return (dt - pdt.datetime(1970, 1, 1)).total_seconds()
xfail_date_bounds = pytest.mark.xfail(
sys.version_info < (3, 6),
@ -103,9 +89,9 @@ def test_invalid_date_fails():
MAX_DATETIME_FROM_TIMESTAMP.date()))
def test_date_from_timestamp(d):
if PYPY and d < pdt.date(1900, 1, 1):
pytest.xfail("get_timestamp will raise on PyPy with dates before 1900")
pytest.xfail("pdt.datetime.timestamp will raise on PyPy with dates before 1900")
ts = get_timestamp(pdt.datetime.combine(d, pdt.time(0)))
ts = pdt.datetime.timestamp(pdt.datetime.combine(d, pdt.time(0)))
assert rdt.date_from_timestamp(int(ts)) == pdt.date.fromtimestamp(ts)
@ -241,11 +227,12 @@ def test_datetime_typeerror():
@given(dt=st.datetimes(MIN_DATETIME_FROM_TIMESTAMP,
MAX_DATETIME_FROM_TIMESTAMP))
@example(dt=pdt.datetime(1970, 1, 2, 0, 0))
def test_datetime_from_timestamp(dt):
if PYPY and dt < pdt.datetime(1900, 1, 1):
pytest.xfail("get_timestamp will raise on PyPy with dates before 1900")
pytest.xfail("pdt.datetime.timestamp will raise on PyPy with dates before 1900")
ts = get_timestamp(dt)
ts = pdt.datetime.timestamp(dt)
assert rdt.datetime_from_timestamp(ts) == pdt.datetime.fromtimestamp(ts)

View file

@ -102,10 +102,10 @@ so that they can benefit from a freelist. `XXX` is a number of items for the fre
If a custom class contains references to other Python objects that can be collected, the `PyGCProtocol` 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.
* `dict` - Adds `__dict__` support, so that the instances of this type have a dictionary containing arbitrary instance variables.
* `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.
* `subclass` - Allows Python classes to inherit from this class. This feature is hidden behind a `unsound-subclass` feature because it is currently causing segmentation faults
## Constructor
@ -323,7 +323,7 @@ impl MyClass {
```
Calls to these methods are protected by the GIL, so both `&self` and `&mut self` can be used.
The return type must be `PyResult<T>` or `T` for some `T` that implements `IntoPyObject`;
The return type must be `PyResult<T>` or `T` for some `T` that implements `IntoPy<PyObject>`;
the latter is allowed if the method cannot raise Python exceptions.
A `Python` parameter can be specified as part of method signature, in this case the `py` argument
@ -376,13 +376,13 @@ Declares a class method callable from Python.
This may be the type object of a derived class.
* The first parameter implicitly has type `&PyType`.
* For details on `parameter-list`, see the documentation of `Method arguments` section.
* The return type must be `PyResult<T>` or `T` for some `T` that implements `IntoPyObject`.
* The return type must be `PyResult<T>` or `T` for some `T` that implements `IntoPy<PyObject>`.
## Static methods
To create a static method for a custom class, the method needs to be annotated with the
`#[staticmethod]` attribute. The return type must be `T` or `PyResult<T>` for some `T` that implements
`IntoPyObject`.
`IntoPy<PyObject>`.
```rust
# use pyo3::prelude::*;
@ -460,8 +460,8 @@ use pyo3::types::{PyDict, PyTuple};
#
#[pymethods]
impl MyClass {
#[args(arg1=true, args="*", arg2=10, kwargs="**")]
fn method(&self, arg1: bool, args: &PyTuple, arg2: i32, kwargs: Option<&PyDict>) -> PyResult<i32> {
#[args(arg1=true, args="*", arg2=10, args3="\"Hello\"", kwargs="**")]
fn method(&self, arg1: bool, args: &PyTuple, arg2: i32, arg3: &str, kwargs: Option<&PyDict>) -> PyResult<i32> {
Ok(1)
}
}
@ -483,7 +483,7 @@ The [`PyObjectProtocol`](https://docs.rs/pyo3/0.7.0/pyo3/class/basic/trait.PyObj
To customize object attribute access, define the following methods:
* `fn __getattr__(&self, name: FromPyObject) -> PyResult<impl IntoPyObject>`
* `fn __getattr__(&self, name: FromPyObject) -> PyResult<impl IntoPy<PyObject>>`
* `fn __setattr__(&mut self, name: FromPyObject, value: FromPyObject) -> PyResult<()>`
* `fn __delattr__(&mut self, name: FromPyObject) -> PyResult<()>`
@ -573,17 +573,24 @@ 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 class using the `gc` parameter of the `pyclass` 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.
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
[`PyIterProtocol`](https://docs.rs/pyo3/0.7.0/pyo3/class/iter/trait.PyIterProtocol.html) trait.
It includes two methods `__iter__` and `__next__`:
* `fn __iter__(slf: PyRefMut<Self>) -> PyResult<impl IntoPyObject>`
* `fn __next__(slf: PyRefMut<Self>) -> PyResult<Option<impl IntoPyObject>>`
* `fn __iter__(slf: PyRefMut<Self>) -> PyResult<impl IntoPy<PyObject>>`
* `fn __next__(slf: PyRefMut<Self>) -> PyResult<Option<impl IntoPy<PyObject>>>`
Returning `Ok(None)` from `__next__` indicates that that there are no further items.

View file

@ -6,10 +6,10 @@ PyO3 provides some handy traits to convert between Python types and Rust types.
The easiest way to convert a Python object to a Rust value is using `.extract()?`.
## `ToPyObject` and `IntoPyObject` trait
## `ToPyObject` trait
[`ToPyObject`] trait is a conversion trait that allows various objects to be
converted into [`PyObject`][PyObject]. [`IntoPyObject`][IntoPyObject] serves the
converted into [`PyObject`][PyObject]. `IntoPy<PyObject>` serves the
same purpose, except that it consumes `self`.
## `FromPyObject` and `RefFromPyObject` trait
@ -104,10 +104,9 @@ fn main() {
Many conversions in PyO3 can't use `std::convert::Into` because they need a GIL token. That's why the `IntoPy<T>` trait offers an `into_py` method that works just like `into`, except for taking a `Python<'_>` argument.
Eventually, traits such as `IntoPyObject` will be replaced by this trait and a `FromPy` trait will be added that will implement `IntoPy`, just like with `From` and `Into`.
Eventually, traits such as `ToPyObject` will be replaced by this trait and a `FromPy` trait will be added that will implement `IntoPy`, just like with `From` and `Into`.
[`ToPyObject`]: https://docs.rs/pyo3/0.7.0/pyo3/trait.ToPyObject.html
[IntoPyObject]: https://docs.rs/pyo3/0.7.0/pyo3/trait.IntoPyObject.html
[PyObject]: https://docs.rs/pyo3/0.7.0/pyo3/struct.PyObject.html
[PyTuple]: https://docs.rs/pyo3/0.7.0/pyo3/types/struct.PyTuple.html
[ObjectProtocol]: https://docs.rs/pyo3/0.7.0/pyo3/trait.ObjectProtocol.html

View file

@ -128,23 +128,39 @@ more complex arguments are required, the
trait can be implemented. In that case, actual exception argument creation is delayed
until a `Python` object is available.
```rust,ignore
use std::net::TcpListener;
use pyo3::{PyErr, PyResult, exc};
```rust
# use pyo3::{exceptions, PyErr, PyResult};
# use std::error::Error;
# use std::fmt;
#
# #[derive(Debug)]
# struct CustomIOError;
#
# impl Error for CustomIOError {}
#
# impl fmt::Display for CustomIOError {
# fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
# write!(f, "Oh no!")
# }
# }
#
# fn bind(_addr: &str) -> Result<(), CustomIOError> {
# Err(CustomIOError)
# }
impl std::convert::From<std::io::Error> for PyErr {
fn from(err: std::io::Error) -> PyErr {
exceptions::OSError.into()
impl std::convert::From<CustomIOError> for PyErr {
fn from(err: CustomIOError) -> PyErr {
exceptions::OSError::py_err(err.to_string())
}
}
fn connect(s: String) -> PyResult<bool> {
TcpListener::bind("127.0.0.1:80")?;
bind("127.0.0.1:80")?;
Ok(true)
}
```
The code snippet above will raise an `OSError` in Python if `TcpListener::bind()` returns an error.
The code snippet above will raise an `OSError` in Python if `bind()` returns a `CustomIOError`.
The `std::convert::From<T>` trait is implemented for most of the Rust standard library's error
types so the `try!` macro or the `?` operator can be used.

View file

@ -81,9 +81,7 @@ In order to make the function signature available to Python to be retrieved via
formatted like in the example below. Please note that the newline after the
`--` is mandatory. The `/` signifies the end of positional-only arguments. This
is not a feature of this library in particular, but the general format used by
CPython for annotating signatures of built-in functions. Function signatures for
built-ins are new in Python 3 — in Python 2, they are simply considered to be a
part of the docstring.
CPython for annotating signatures of built-in functions.
```rust
use pyo3::prelude::*;

View file

@ -10,7 +10,7 @@ A comparison with rust-cpython can be found [in the guide](https://pyo3.rs/maste
## Usage
PyO3 supports Python 3.5 and up. The minimum required Rust version is 1.34.0-nightly 2019-02-06.
PyO3 supports Python 3.5 and up. The minimum required Rust version is 1.37.0-nightly 2019-07-19.
PyPy is also supported (via cpyext) for Python 3.5 only, targeted PyPy version is 7.0.0.
Please refer to the guide for installation instruction against PyPy.

View file

@ -11,6 +11,9 @@ license = "Apache-2.0"
edition = "2018"
[dependencies]
quote = "0.6.12"
proc-macro2 = "0.4.30"
syn = { version = "0.15.34", features = ["full", "extra-traits"] }
quote = "1"
proc-macro2 = "1"
syn = { version = "1", features = ["full", "extra-traits"] }
[features]
unsound-subclass = []

View file

@ -2,6 +2,7 @@
use crate::utils::print_err;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::Token;
// TODO:
// Add lifetime support for args with Rptr
@ -70,7 +71,7 @@ impl PartialEq<str> for MethodProto {
pub fn impl_method_proto(
cls: &syn::Type,
sig: &mut syn::MethodSig,
sig: &mut syn::Signature,
meth: &MethodProto,
) -> TokenStream {
if let MethodProto::Free { proto, .. } = meth {
@ -80,7 +81,7 @@ pub fn impl_method_proto(
};
}
let ty = &*if let syn::ReturnType::Type(_, ref ty) = sig.decl.output {
let ty = &*if let syn::ReturnType::Type(_, ref ty) = sig.output {
ty.clone()
} else {
panic!("fn return type is not supported")
@ -95,7 +96,7 @@ pub fn impl_method_proto(
let tmp: syn::ItemFn = syn::parse_quote! {
fn test(&self) -> <#cls as #p<'p>>::Result {}
};
sig.decl.output = tmp.decl.output;
sig.output = tmp.sig.output;
modify_self_ty(sig);
if pyres {
@ -119,7 +120,7 @@ pub fn impl_method_proto(
pyres,
proto,
} => {
if sig.decl.inputs.len() <= 1 {
if sig.inputs.len() <= 1 {
println!("Not enough arguments for {}", name);
return TokenStream::new();
}
@ -164,7 +165,7 @@ pub fn impl_method_proto(
pyres,
proto,
} => {
if sig.decl.inputs.len() <= 1 {
if sig.inputs.len() <= 1 {
print_err(format!("Not enough arguments {}", name), quote!(sig));
return TokenStream::new();
}
@ -213,7 +214,7 @@ pub fn impl_method_proto(
pyres,
proto,
} => {
if sig.decl.inputs.len() <= 2 {
if sig.inputs.len() <= 2 {
print_err(format!("Not enough arguments {}", name), quote!(sig));
return TokenStream::new();
}
@ -266,7 +267,7 @@ pub fn impl_method_proto(
pyres,
proto,
} => {
if sig.decl.inputs.len() <= 2 {
if sig.inputs.len() <= 2 {
print_err(format!("Not enough arguments {}", name), quote!(sig));
return TokenStream::new();
}
@ -322,7 +323,7 @@ pub fn impl_method_proto(
arg3,
proto,
} => {
if sig.decl.inputs.len() <= 3 {
if sig.inputs.len() <= 3 {
print_err(format!("Not enough arguments {}", name), quote!(sig));
return TokenStream::new();
}
@ -367,26 +368,26 @@ pub fn impl_method_proto(
}
// TODO: better arg ty detection
fn get_arg_ty(sig: &syn::MethodSig, idx: usize) -> syn::Type {
let mut ty = match sig.decl.inputs[idx] {
syn::FnArg::Captured(ref cap) => {
match cap.ty {
fn get_arg_ty(sig: &syn::Signature, idx: usize) -> syn::Type {
let mut ty = match sig.inputs[idx] {
syn::FnArg::Typed(ref cap) => {
match *cap.ty {
syn::Type::Path(ref ty) => {
// use only last path segment for Option<>
let seg = *ty.path.segments.last().unwrap().value();
let seg = ty.path.segments.last().unwrap().clone();
if seg.ident == "Option" {
if let syn::PathArguments::AngleBracketed(ref data) = seg.arguments {
if let Some(pair) = data.args.last() {
match pair.value() {
match pair {
syn::GenericArgument::Type(ref ty) => return ty.clone(),
_ => panic!("Option only accepted for concrete types"),
}
};
}
}
cap.ty.clone()
*cap.ty.clone()
}
_ => cap.ty.clone(),
_ => *cap.ty.clone(),
}
}
_ => panic!("fn arg type is not supported"),
@ -408,9 +409,9 @@ fn get_res_success(ty: &syn::Type) -> (TokenStream, syn::GenericArgument) {
match ty {
syn::Type::Path(ref typath) => {
if let Some(segment) = typath.path.segments.last() {
match segment.value().ident.to_string().as_str() {
match segment.ident.to_string().as_str() {
// check for PyResult<T>
"PyResult" => match segment.value().arguments {
"PyResult" => match segment.arguments {
syn::PathArguments::AngleBracketed(ref data) => {
result = true;
succ = data.args[0].clone();
@ -420,10 +421,10 @@ fn get_res_success(ty: &syn::Type) -> (TokenStream, syn::GenericArgument) {
data.args[0]
{
if let Some(segment) = typath.path.segments.last() {
if "Option" == segment.value().ident.to_string().as_str() {
if "Option" == segment.ident.to_string().as_str() {
// get T from Option<T>
if let syn::PathArguments::AngleBracketed(ref data) =
segment.value().arguments
segment.arguments
{
result = false;
succ = data.args[0].clone();
@ -436,7 +437,7 @@ fn get_res_success(ty: &syn::Type) -> (TokenStream, syn::GenericArgument) {
},
_ => panic!(
"fn result type has to be PyResult or (), got {:?}",
segment.value().ident
segment.ident
),
}
} else {
@ -456,50 +457,55 @@ fn get_res_success(ty: &syn::Type) -> (TokenStream, syn::GenericArgument) {
(res, succ)
}
fn extract_decl(spec: syn::Item) -> syn::FnDecl {
fn extract_decl(spec: syn::Item) -> syn::Signature {
match spec {
syn::Item::Fn(f) => *f.decl,
syn::Item::Fn(f) => f.sig,
_ => panic!(),
}
}
// modify method signature
fn modify_arg_ty(sig: &mut syn::MethodSig, idx: usize, decl1: &syn::FnDecl, decl2: &syn::FnDecl) {
let arg = sig.decl.inputs[idx].clone();
fn modify_arg_ty(
sig: &mut syn::Signature,
idx: usize,
decl1: &syn::Signature,
decl2: &syn::Signature,
) {
let arg = sig.inputs[idx].clone();
match arg {
syn::FnArg::Captured(ref cap) => match cap.ty {
syn::FnArg::Typed(ref cap) => match *cap.ty {
syn::Type::Path(ref typath) => {
let seg = *typath.path.segments.last().unwrap().value();
let seg = typath.path.segments.last().unwrap().clone();
if seg.ident == "Option" {
sig.decl.inputs[idx] = fix_name(&cap.pat, &decl2.inputs[idx]);
sig.inputs[idx] = fix_name(&cap.pat, &decl2.inputs[idx]);
} else {
sig.decl.inputs[idx] = fix_name(&cap.pat, &decl1.inputs[idx]);
sig.inputs[idx] = fix_name(&cap.pat, &decl1.inputs[idx]);
}
}
_ => {
sig.decl.inputs[idx] = fix_name(&cap.pat, &decl1.inputs[idx]);
sig.inputs[idx] = fix_name(&cap.pat, &decl1.inputs[idx]);
}
},
_ => panic!("not supported"),
}
sig.decl.output = decl1.output.clone();
sig.output = decl1.output.clone();
}
fn modify_self_ty(sig: &mut syn::MethodSig) {
match sig.decl.inputs[0] {
syn::FnArg::SelfRef(ref mut slf) => {
slf.lifetime = Some(syn::parse_quote! {'p});
fn modify_self_ty(sig: &mut syn::Signature) {
match sig.inputs[0] {
syn::FnArg::Receiver(ref mut slf) => {
slf.reference = Some((Token![&](Span::call_site()), syn::parse_quote! {'p}));
}
syn::FnArg::Captured(_) => {}
_ => panic!("not supported"),
syn::FnArg::Typed(_) => {}
}
}
fn fix_name(pat: &syn::Pat, arg: &syn::FnArg) -> syn::FnArg {
if let syn::FnArg::Captured(ref cap) = arg {
syn::FnArg::Captured(syn::ArgCaptured {
pat: pat.clone(),
if let syn::FnArg::Typed(ref cap) = arg {
syn::FnArg::Typed(syn::PatType {
attrs: cap.attrs.clone(),
pat: Box::new(pat.clone()),
colon_token: cap.colon_token,
ty: cap.ty.clone(),
})

View file

@ -49,22 +49,19 @@ impl<'a> FnSpec<'a> {
/// Parser function signature and function attributes
pub fn parse(
name: &'a syn::Ident,
sig: &'a syn::MethodSig,
sig: &'a syn::Signature,
meth_attrs: &'a mut Vec<syn::Attribute>,
) -> syn::Result<FnSpec<'a>> {
let (mut fn_type, fn_attrs) = parse_attributes(meth_attrs)?;
let mut has_self = false;
let mut arguments = Vec::new();
for input in sig.decl.inputs.iter() {
for input in sig.inputs.iter() {
match input {
syn::FnArg::SelfRef(_) => {
syn::FnArg::Receiver(_) => {
has_self = true;
}
syn::FnArg::SelfValue(_) => {
has_self = true;
}
syn::FnArg::Captured(syn::ArgCaptured {
syn::FnArg::Typed(syn::PatType {
ref pat, ref ty, ..
}) => {
// skip first argument (cls)
@ -73,7 +70,7 @@ impl<'a> FnSpec<'a> {
continue;
}
let (ident, by_ref, mutability) = match pat {
let (ident, by_ref, mutability) = match **pat {
syn::Pat::Ident(syn::PatIdent {
ref ident,
ref by_ref,
@ -85,16 +82,7 @@ impl<'a> FnSpec<'a> {
}
};
let py = match ty {
syn::Type::Path(syn::TypePath { ref path, .. }) => {
if let Some(segment) = path.segments.last() {
segment.value().ident == "Python"
} else {
false
}
}
_ => false,
};
let py = crate::utils::if_type_is_python(ty);
let opt = check_arg_ty_and_optional(name, ty);
arguments.push(FnArg {
@ -108,16 +96,10 @@ impl<'a> FnSpec<'a> {
reference: is_ref(name, ty),
});
}
syn::FnArg::Ignored(_) => {
return Err(syn::Error::new_spanned(name, "ignored argument"));
}
syn::FnArg::Inferred(_) => {
return Err(syn::Error::new_spanned(name, "inferred argument"));
}
}
}
let ty = get_return_info(&sig.decl.output);
let ty = get_return_info(&sig.output);
if fn_type == FnType::Fn && !has_self {
if arguments.is_empty() {
@ -140,8 +122,8 @@ impl<'a> FnSpec<'a> {
pub fn is_args(&self, name: &syn::Ident) -> bool {
for s in self.attrs.iter() {
if let Argument::VarArgs(ref ident) = s {
return name == ident;
if let Argument::VarArgs(ref path) = s {
return path.is_ident(name);
}
}
false
@ -160,8 +142,8 @@ impl<'a> FnSpec<'a> {
pub fn is_kwargs(&self, name: &syn::Ident) -> bool {
for s in self.attrs.iter() {
if let Argument::KeywordArgs(ref ident) = s {
return name == ident;
if let Argument::KeywordArgs(ref path) = s {
return path.is_ident(name);
}
}
false
@ -179,16 +161,16 @@ impl<'a> FnSpec<'a> {
pub fn default_value(&self, name: &syn::Ident) -> Option<TokenStream> {
for s in self.attrs.iter() {
match *s {
Argument::Arg(ref ident, ref opt) => {
if ident == name {
Argument::Arg(ref path, ref opt) => {
if path.is_ident(name) {
if let Some(ref val) = opt {
let i: syn::Expr = syn::parse_str(&val).unwrap();
return Some(i.into_token_stream());
}
}
}
Argument::Kwarg(ref ident, ref opt) => {
if ident == name {
Argument::Kwarg(ref path, ref opt) => {
if path.is_ident(name) {
let i: syn::Expr = syn::parse_str(&opt).unwrap();
return Some(quote!(#i));
}
@ -201,8 +183,8 @@ impl<'a> FnSpec<'a> {
pub fn is_kw_only(&self, name: &syn::Ident) -> bool {
for s in self.attrs.iter() {
if let Argument::Kwarg(ref ident, _) = s {
if ident == name {
if let Argument::Kwarg(ref path, _) = s {
if path.is_ident(name) {
return true;
}
}
@ -216,8 +198,8 @@ pub fn is_ref(name: &syn::Ident, ty: &syn::Type) -> bool {
syn::Type::Reference(_) => return true,
syn::Type::Path(syn::TypePath { ref path, .. }) => {
if let Some(segment) = path.segments.last() {
if "Option" == segment.value().ident.to_string().as_str() {
match segment.value().arguments {
if "Option" == segment.ident.to_string().as_str() {
match segment.arguments {
syn::PathArguments::AngleBracketed(ref params) => {
if params.args.len() != 1 {
panic!("argument type is not supported by python method: {:?} ({:?}) {:?}",
@ -257,8 +239,8 @@ pub fn check_arg_ty_and_optional<'a>(
//}
if let Some(segment) = path.segments.last() {
match segment.value().ident.to_string().as_str() {
"Option" => match segment.value().arguments {
match segment.ident.to_string().as_str() {
"Option" => match segment.arguments {
syn::PathArguments::AngleBracketed(ref params) => {
if params.args.len() != 1 {
panic!("argument type is not supported by python method: {:?} ({:?}) {:?}",
@ -303,40 +285,46 @@ fn parse_attributes(attrs: &mut Vec<syn::Attribute>) -> syn::Result<(FnType, Vec
let mut res: Option<FnType> = None;
for attr in attrs.iter() {
match attr.interpret_meta().unwrap() {
syn::Meta::Word(ref name) => match name.to_string().as_ref() {
"new" | "__new__" => res = Some(FnType::FnNew),
"init" | "__init__" => res = Some(FnType::FnInit),
"call" | "__call__" => res = Some(FnType::FnCall),
"classmethod" => res = Some(FnType::FnClass),
"staticmethod" => res = Some(FnType::FnStatic),
"setter" | "getter" => {
match attr.parse_meta().unwrap() {
syn::Meta::Path(ref name) => {
if name.is_ident("new") || name.is_ident("__new__") {
res = Some(FnType::FnNew)
} else if name.is_ident("init") || name.is_ident("__init__") {
res = Some(FnType::FnInit)
} else if name.is_ident("call") || name.is_ident("__call__") {
res = Some(FnType::FnCall)
} else if name.is_ident("classmethod") {
res = Some(FnType::FnClass)
} else if name.is_ident("staticmethod") {
res = Some(FnType::FnStatic)
} else if name.is_ident("setter") || name.is_ident("getter") {
if let syn::AttrStyle::Inner(_) = attr.style {
panic!(
"Inner style attribute is not
supported for setter and getter"
);
panic!("Inner style attribute is not supported for setter and getter");
}
if res != None {
panic!("setter/getter attribute can not be used mutiple times");
}
if name == "setter" {
if name.is_ident("setter") {
res = Some(FnType::Setter(None))
} else {
res = Some(FnType::Getter(None))
}
} else {
new_attrs.push(attr.clone())
}
_ => new_attrs.push(attr.clone()),
},
}
syn::Meta::List(syn::MetaList {
ref ident,
ref path,
ref nested,
..
}) => match ident.to_string().as_str() {
"new" => res = Some(FnType::FnNew),
"init" => res = Some(FnType::FnInit),
"call" => res = Some(FnType::FnCall),
"setter" | "getter" => {
}) => {
if path.is_ident("new") {
res = Some(FnType::FnNew)
} else if path.is_ident("init") {
res = Some(FnType::FnInit)
} else if path.is_ident("call") {
res = Some(FnType::FnCall)
} else if path.is_ident("setter") || path.is_ident("getter") {
if let syn::AttrStyle::Inner(_) = attr.style {
panic!(
"Inner style attribute is not
@ -349,17 +337,17 @@ fn parse_attributes(attrs: &mut Vec<syn::Attribute>) -> syn::Result<(FnType, Vec
if nested.len() != 1 {
panic!("setter/getter requires one value");
}
match nested.first().unwrap().value() {
syn::NestedMeta::Meta(syn::Meta::Word(ref w)) => {
if ident == "setter" {
res = Some(FnType::Setter(Some(w.to_string())))
match nested.first().unwrap() {
syn::NestedMeta::Meta(syn::Meta::Path(ref w)) => {
if path.is_ident("setter") {
res = Some(FnType::Setter(Some(w.segments[0].ident.to_string())))
} else {
res = Some(FnType::Getter(Some(w.to_string())))
res = Some(FnType::Getter(Some(w.segments[0].ident.to_string())))
}
}
syn::NestedMeta::Literal(ref lit) => match *lit {
syn::NestedMeta::Lit(ref lit) => match *lit {
syn::Lit::Str(ref s) => {
if ident == "setter" {
if path.is_ident("setter") {
res = Some(FnType::Setter(Some(s.value())))
} else {
res = Some(FnType::Getter(Some(s.value())))
@ -370,16 +358,16 @@ fn parse_attributes(attrs: &mut Vec<syn::Attribute>) -> syn::Result<(FnType, Vec
}
},
_ => {
println!("cannot parse {:?} attribute: {:?}", ident, nested);
println!("cannot parse {:?} attribute: {:?}", path, nested);
}
}
}
"args" => {
} else if path.is_ident("args") {
let attrs = PyFunctionAttr::from_meta(nested)?;
spec.extend(attrs.arguments)
} else {
new_attrs.push(attr.clone())
}
_ => new_attrs.push(attr.clone()),
},
}
syn::Meta::NameValue(_) => new_attrs.push(attr.clone()),
}
}

View file

@ -8,8 +8,7 @@ use crate::pymethod;
use crate::pymethod::get_arg_names;
use crate::utils;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::ext::IdentExt;
use quote::{format_ident, quote};
use syn::Ident;
/// Generates the function that is called by the python interpreter to initialize the native
@ -38,7 +37,7 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) {
extract_pyfn_attrs(&mut func.attrs)
{
let function_to_python = add_fn_to_module(func, &python_name, pyfn_attrs);
let function_wrapper_ident = function_wrapper_ident(&func.ident);
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
let item: syn::ItemFn = syn::parse_quote! {
fn block_wrapper() {
#function_to_python
@ -57,23 +56,14 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) {
/// Transforms a rust fn arg parsed with syn into a method::FnArg
fn wrap_fn_argument<'a>(input: &'a syn::FnArg, name: &'a Ident) -> Option<method::FnArg<'a>> {
match input {
syn::FnArg::SelfRef(_) | &syn::FnArg::SelfValue(_) => None,
syn::FnArg::Captured(ref cap) => {
let (mutability, by_ref, ident) = match cap.pat {
syn::FnArg::Receiver(_) => None,
syn::FnArg::Typed(ref cap) => {
let (mutability, by_ref, ident) = match *cap.pat {
syn::Pat::Ident(ref patid) => (&patid.mutability, &patid.by_ref, &patid.ident),
_ => panic!("unsupported argument: {:?}", cap.pat),
};
let py = match cap.ty {
syn::Type::Path(ref typath) => typath
.path
.segments
.last()
.map(|seg| seg.value().ident == "Python")
.unwrap_or(false),
_ => false,
};
let py = crate::utils::if_type_is_python(&cap.ty);
let opt = method::check_arg_ty_and_optional(&name, &cap.ty);
Some(method::FnArg {
name: ident,
@ -85,15 +75,13 @@ fn wrap_fn_argument<'a>(input: &'a syn::FnArg, name: &'a Ident) -> Option<method
reference: method::is_ref(&name, &cap.ty),
})
}
syn::FnArg::Ignored(_) => panic!("ignored argument: {:?}", name),
syn::FnArg::Inferred(_) => panic!("inferred argument: {:?}", name),
}
}
/// Extracts the data from the #[pyfn(...)] attribute of a function
fn extract_pyfn_attrs(
attrs: &mut Vec<syn::Attribute>,
) -> Option<(Ident, Ident, Vec<pyfunction::Argument>)> {
) -> Option<(syn::Path, Ident, Vec<pyfunction::Argument>)> {
let mut new_attrs = Vec::new();
let mut fnname = None;
let mut modname = None;
@ -101,19 +89,19 @@ fn extract_pyfn_attrs(
for attr in attrs.iter() {
match attr.parse_meta() {
Ok(syn::Meta::List(ref list)) if list.ident == "pyfn" => {
Ok(syn::Meta::List(ref list)) if list.path.is_ident("pyfn") => {
let meta: Vec<_> = list.nested.iter().cloned().collect();
if meta.len() >= 2 {
// read module name
match meta[0] {
syn::NestedMeta::Meta(syn::Meta::Word(ref ident)) => {
modname = Some(ident.clone())
syn::NestedMeta::Meta(syn::Meta::Path(ref path)) => {
modname = Some(path.clone())
}
_ => panic!("The first parameter of pyfn must be a MetaItem"),
}
// read Python fonction name
// read Python function name
match meta[1] {
syn::NestedMeta::Literal(syn::Lit::Str(ref lits)) => {
syn::NestedMeta::Lit(syn::Lit::Str(ref lits)) => {
fnname = Some(syn::Ident::new(&lits.value(), lits.span()));
}
_ => panic!("The second parameter of pyfn must be a Literal"),
@ -139,10 +127,7 @@ fn extract_pyfn_attrs(
/// Coordinates the naming of a the add-function-to-python-module function
fn function_wrapper_ident(name: &Ident) -> Ident {
// Make sure this ident matches the one of wrap_pyfunction
Ident::new(
&format!("__pyo3_get_function_{}", name.unraw().to_string()),
Span::call_site(),
)
format_ident!("__pyo3_get_function_{}", name)
}
/// Generates python wrapper over a function that allows adding it to a python module as a python
@ -154,13 +139,13 @@ pub fn add_fn_to_module(
) -> TokenStream {
let mut arguments = Vec::new();
for input in func.decl.inputs.iter() {
if let Some(fn_arg) = wrap_fn_argument(input, &func.ident) {
for input in func.sig.inputs.iter() {
if let Some(fn_arg) = wrap_fn_argument(input, &func.sig.ident) {
arguments.push(fn_arg);
}
}
let ty = method::get_return_info(&func.decl.output);
let ty = method::get_return_info(&func.sig.output);
let spec = method::FnSpec {
tp: method::FnType::Fn,
@ -169,9 +154,9 @@ pub fn add_fn_to_module(
output: ty,
};
let function_wrapper_ident = function_wrapper_ident(&func.ident);
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
let wrapper = function_c_wrapper(&func.ident, &spec);
let wrapper = function_c_wrapper(&func.sig.ident, &spec);
let doc = utils::get_doc(&func.attrs, true);
let tokens = quote! {

View file

@ -59,7 +59,7 @@ impl PyClassArgs {
fn add_assign(&mut self, assign: &syn::ExprAssign) -> syn::Result<()> {
let key = match *assign.left {
syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => {
exp.path.segments.first().unwrap().value().ident.to_string()
exp.path.segments.first().unwrap().ident.to_string()
}
_ => {
return Err(syn::Error::new_spanned(assign, "could not parse argument"));
@ -123,7 +123,7 @@ impl PyClassArgs {
/// Match a key/value flag
fn add_path(&mut self, exp: &syn::ExprPath) -> syn::Result<()> {
let flag = exp.path.segments.first().unwrap().value().ident.to_string();
let flag = exp.path.segments.first().unwrap().ident.to_string();
let path = match flag.as_str() {
"gc" => {
parse_quote! {pyo3::type_object::PY_TYPE_FLAG_GC}
@ -132,6 +132,12 @@ impl PyClassArgs {
parse_quote! {pyo3::type_object::PY_TYPE_FLAG_WEAKREF}
}
"subclass" => {
if cfg!(not(feature = "unsound-subclass")) {
return Err(syn::Error::new_spanned(
exp.path.clone(),
"You need to activate the `unsound-subclass` feature if you want to use subclassing",
));
}
parse_quote! {pyo3::type_object::PY_TYPE_FLAG_BASETYPE}
}
"dict" => {
@ -178,28 +184,23 @@ fn parse_descriptors(item: &mut syn::Field) -> syn::Result<Vec<FnType>> {
let mut new_attrs = Vec::new();
for attr in item.attrs.iter() {
if let Ok(syn::Meta::List(ref list)) = attr.parse_meta() {
match list.ident.to_string().as_str() {
"pyo3" => {
for meta in list.nested.iter() {
if let syn::NestedMeta::Meta(ref metaitem) = meta {
match metaitem.name().to_string().as_str() {
"get" => {
descs.push(FnType::Getter(None));
}
"set" => {
descs.push(FnType::Setter(None));
}
_ => {
return Err(syn::Error::new_spanned(
metaitem,
"Only get and set are supported",
));
}
}
if list.path.is_ident("pyo3") {
for meta in list.nested.iter() {
if let syn::NestedMeta::Meta(ref metaitem) = meta {
if metaitem.path().is_ident("get") {
descs.push(FnType::Getter(None));
} else if metaitem.path().is_ident("set") {
descs.push(FnType::Setter(None));
} else {
return Err(syn::Error::new_spanned(
metaitem,
"Only get and set are supported",
));
}
}
}
_ => new_attrs.push(attr.clone()),
} else {
new_attrs.push(attr.clone())
}
} else {
new_attrs.push(attr.clone());
@ -296,12 +297,15 @@ fn impl_class(
// insert space for weak ref
let mut has_weakref = false;
let mut has_dict = false;
let mut has_gc = false;
for f in attr.flags.iter() {
if let syn::Expr::Path(ref epath) = f {
if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_WEAKREF} {
has_weakref = true;
} else if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_DICT} {
has_dict = true;
} else if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_GC} {
has_gc = true;
}
}
}
@ -321,6 +325,22 @@ fn impl_class(
quote! { None }
};
// Enforce at compile time that PyGCProtocol is implemented
let gc_impl = if has_gc {
let closure_name = format!("__assertion_closure_{}", cls.to_string());
let closure_token = syn::Ident::new(&closure_name, Span::call_site());
quote! {
fn #closure_token() {
use pyo3::class;
fn _assert_implements_protocol<'p, T: pyo3::class::PyGCProtocol<'p>>() {}
_assert_implements_protocol::<#cls>();
}
}
} else {
quote! {}
};
let inventory_impl = impl_inventory(&cls);
let base = &attr.base;
@ -356,15 +376,18 @@ fn impl_class(
}
}
impl pyo3::IntoPyObject for #cls {
fn into_object(self, py: pyo3::Python) -> pyo3::PyObject {
pyo3::Py::new(py, self).unwrap().into_object(py)
impl pyo3::IntoPy<PyObject> for #cls {
fn into_py(self, py: pyo3::Python) -> pyo3::PyObject {
pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py)
}
}
#inventory_impl
#extra
#gc_impl
}
}
@ -417,9 +440,12 @@ fn impl_descriptors(cls: &syn::Type, descriptors: Vec<(syn::Field, Vec<FnType>)>
let field_ty = &field.ty;
match *desc {
FnType::Getter(ref getter) => {
impl_py_getter_def(&name, doc, getter, &impl_wrap_getter(&cls, &name))
}
FnType::Getter(ref getter) => impl_py_getter_def(
&name,
doc,
getter,
&impl_wrap_getter(&cls, &name, false),
),
FnType::Setter(ref setter) => {
let setter_name =
syn::Ident::new(&format!("set_{}", name), Span::call_site());

View file

@ -2,15 +2,15 @@
use syn::parse::ParseBuffer;
use syn::punctuated::Punctuated;
use syn::{Ident, NestedMeta};
use syn::{NestedMeta, Path};
#[derive(Debug, Clone, PartialEq)]
pub enum Argument {
VarArgsSeparator,
VarArgs(syn::Ident),
KeywordArgs(syn::Ident),
Arg(syn::Ident, Option<String>),
Kwarg(syn::Ident, String),
VarArgs(syn::Path),
KeywordArgs(syn::Path),
Arg(syn::Path, Option<String>),
Kwarg(syn::Path, String),
}
/// The attributes of the pyfunction macro
@ -41,11 +41,11 @@ impl PyFunctionAttr {
pub fn add_item(&mut self, item: &NestedMeta) -> syn::Result<()> {
match item {
NestedMeta::Meta(syn::Meta::Word(ref ident)) => self.add_work(item, ident)?,
NestedMeta::Meta(syn::Meta::Path(ref ident)) => self.add_work(item, ident)?,
NestedMeta::Meta(syn::Meta::NameValue(ref nv)) => {
self.add_name_value(item, nv)?;
}
NestedMeta::Literal(ref lit) => {
NestedMeta::Lit(ref lit) => {
self.add_literal(item, lit)?;
}
_ => {
@ -89,7 +89,7 @@ impl PyFunctionAttr {
Ok(())
}
fn add_work(&mut self, item: &NestedMeta, ident: &Ident) -> syn::Result<()> {
fn add_work(&mut self, item: &NestedMeta, path: &Path) -> syn::Result<()> {
// self.arguments in form somename
if self.has_kwargs {
return Err(syn::Error::new_spanned(
@ -103,7 +103,7 @@ impl PyFunctionAttr {
"syntax error, argument is not allowed after keyword argument",
));
}
self.arguments.push(Argument::Arg(ident.clone(), None));
self.arguments.push(Argument::Arg(path.clone(), None));
Ok(())
}
@ -122,7 +122,7 @@ impl PyFunctionAttr {
return Err(syn::Error::new_spanned(item, "*(var args) is defined"));
}
self.has_varargs = true;
self.arguments.push(Argument::VarArgs(nv.ident.clone()));
self.arguments.push(Argument::VarArgs(nv.path.clone()));
} else if litstr.value() == "**" {
// kwargs="**"
if self.has_kwargs {
@ -132,10 +132,10 @@ impl PyFunctionAttr {
));
}
self.has_kwargs = true;
self.arguments.push(Argument::KeywordArgs(nv.ident.clone()));
self.arguments.push(Argument::KeywordArgs(nv.path.clone()));
} else if self.has_varargs {
self.arguments
.push(Argument::Kwarg(nv.ident.clone(), litstr.value().clone()))
.push(Argument::Kwarg(nv.path.clone(), litstr.value()))
} else {
if self.has_kwargs {
return Err(syn::Error::new_spanned(
@ -144,18 +144,14 @@ impl PyFunctionAttr {
));
}
self.has_kw = true;
self.arguments.push(Argument::Arg(
nv.ident.clone(),
Some(litstr.value().clone()),
))
self.arguments
.push(Argument::Arg(nv.path.clone(), Some(litstr.value())))
}
}
syn::Lit::Int(ref litint) => {
if self.has_varargs {
self.arguments.push(Argument::Kwarg(
nv.ident.clone(),
format!("{}", litint.value()),
));
self.arguments
.push(Argument::Kwarg(nv.path.clone(), format!("{}", litint)));
} else {
if self.has_kwargs {
return Err(syn::Error::new_spanned(
@ -164,16 +160,14 @@ impl PyFunctionAttr {
));
}
self.has_kw = true;
self.arguments.push(Argument::Arg(
nv.ident.clone(),
Some(format!("{}", litint.value())),
));
self.arguments
.push(Argument::Arg(nv.path.clone(), Some(format!("{}", litint))));
}
}
syn::Lit::Bool(ref litb) => {
if self.has_varargs {
self.arguments
.push(Argument::Kwarg(nv.ident.clone(), format!("{}", litb.value)));
.push(Argument::Kwarg(nv.path.clone(), format!("{}", litb.value)));
} else {
if self.has_kwargs {
return Err(syn::Error::new_spanned(
@ -183,7 +177,7 @@ impl PyFunctionAttr {
}
self.has_kw = true;
self.arguments.push(Argument::Arg(
nv.ident.clone(),
nv.path.clone(),
Some(format!("{}", litb.value)),
));
}

View file

@ -16,11 +16,11 @@ pub fn build_py_methods(ast: &mut syn::ItemImpl) -> syn::Result<TokenStream> {
"#[pymethods] can not be used with lifetime parameters or generics",
))
} else {
Ok(impl_methods(&ast.self_ty, &mut ast.items))
impl_methods(&ast.self_ty, &mut ast.items)
}
}
pub fn impl_methods(ty: &syn::Type, impls: &mut Vec<syn::ImplItem>) -> TokenStream {
pub fn impl_methods(ty: &syn::Type, impls: &mut Vec<syn::ImplItem>) -> syn::Result<TokenStream> {
// get method names in impl block
let mut methods = Vec::new();
for iimpl in impls.iter_mut() {
@ -31,16 +31,16 @@ pub fn impl_methods(ty: &syn::Type, impls: &mut Vec<syn::ImplItem>) -> TokenStre
&name,
&mut meth.sig,
&mut meth.attrs,
));
)?);
}
}
quote! {
Ok(quote! {
pyo3::inventory::submit! {
#![crate = pyo3] {
type TyInventory = <#ty as pyo3::class::methods::PyMethodsInventoryDispatch>::InventoryType;
<TyInventory as pyo3::class::methods::PyMethodsInventory>::new(&[#(#methods),*])
}
}
}
})
}

View file

@ -7,15 +7,15 @@ use quote::quote;
pub fn gen_py_method(
cls: &syn::Type,
name: &syn::Ident,
sig: &mut syn::MethodSig,
sig: &mut syn::Signature,
meth_attrs: &mut Vec<syn::Attribute>,
) -> TokenStream {
check_generic(name, sig);
) -> syn::Result<TokenStream> {
check_generic(name, sig)?;
let doc = utils::get_doc(&meth_attrs, true);
let spec = FnSpec::parse(name, sig, meth_attrs).unwrap();
let spec = FnSpec::parse(name, sig, meth_attrs)?;
match spec.tp {
Ok(match spec.tp {
FnType::Fn => impl_py_method_def(name, doc, &spec, &impl_wrap(cls, name, &spec, true)),
FnType::PySelf(ref self_ty) => impl_py_method_def(
name,
@ -31,28 +31,43 @@ pub fn gen_py_method(
impl_py_method_def_static(name, doc, &impl_wrap_static(cls, name, &spec))
}
FnType::Getter(ref getter) => {
impl_py_getter_def(name, doc, getter, &impl_wrap_getter(cls, name))
let takes_py = match &*spec.args {
[] => false,
[arg] if utils::if_type_is_python(arg.ty) => true,
_ => {
return Err(syn::Error::new_spanned(
spec.args[0].ty,
"Getter function can only have one argument of type pyo3::Python!",
))
}
};
impl_py_getter_def(name, doc, getter, &impl_wrap_getter(cls, name, takes_py))
}
FnType::Setter(ref setter) => {
impl_py_setter_def(name, doc, setter, &impl_wrap_setter(cls, name, &spec))
}
}
})
}
fn check_generic(name: &syn::Ident, sig: &syn::MethodSig) {
for param in &sig.decl.generics.params {
fn check_generic(name: &syn::Ident, sig: &syn::Signature) -> syn::Result<()> {
let err_msg = |typ| {
format!(
"A Python method can't have a generic {} parameter: {}",
name, typ
)
};
for param in &sig.generics.params {
match param {
syn::GenericParam::Lifetime(_) => {}
syn::GenericParam::Type(_) => panic!(
"A Python method can't have a generic type parameter: {}",
name
),
syn::GenericParam::Const(_) => panic!(
"A Python method can't have a const generic parameter: {}",
name
),
syn::GenericParam::Type(_) => {
return Err(syn::Error::new_spanned(param, err_msg("type")))
}
syn::GenericParam::Const(_) => {
return Err(syn::Error::new_spanned(param, err_msg("const")))
}
}
}
Ok(())
}
/// Generate function wrapper (PyCFunction, PyCFunctionWithKeywords)
@ -302,7 +317,12 @@ pub fn impl_wrap_static(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -
}
/// Generate functiona wrapper (PyCFunction, PyCFunctionWithKeywords)
pub(crate) fn impl_wrap_getter(cls: &syn::Type, name: &syn::Ident) -> TokenStream {
pub(crate) fn impl_wrap_getter(cls: &syn::Type, name: &syn::Ident, takes_py: bool) -> TokenStream {
let fncall = if takes_py {
quote! { _slf.#name(_py) }
} else {
quote! { _slf.#name() }
};
quote! {
unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject, _: *mut ::std::os::raw::c_void) -> *mut pyo3::ffi::PyObject
@ -313,11 +333,11 @@ pub(crate) fn impl_wrap_getter(cls: &syn::Type, name: &syn::Ident) -> TokenStrea
let _py = pyo3::Python::assume_gil_acquired();
let _slf = _py.mut_from_borrowed_ptr::<#cls>(_slf);
let result = pyo3::derive_utils::IntoPyResult::into_py_result(_slf.#name());
let result = pyo3::derive_utils::IntoPyResult::into_py_result(#fncall);
match result {
Ok(val) => {
pyo3::IntoPyPointer::into_ptr(val.into_object(_py))
pyo3::IntoPyPointer::into_ptr(pyo3::IntoPy::<PyObject>::into_py(val, _py))
}
Err(e) => {
e.restore(_py);

View file

@ -12,7 +12,7 @@ use quote::ToTokens;
pub fn build_py_proto(ast: &mut syn::ItemImpl) -> syn::Result<TokenStream> {
if let Some((_, ref mut path, _)) = ast.trait_ {
let proto = if let Some(ref mut segment) = path.segments.last() {
match segment.value().ident.to_string().as_str() {
match segment.ident.to_string().as_str() {
"PyObjectProtocol" => &defs::OBJECT,
"PyAsyncProtocol" => &defs::ASYNC,
"PyMappingProtocol" => &defs::MAPPING,
@ -47,10 +47,10 @@ pub fn build_py_proto(ast: &mut syn::ItemImpl) -> syn::Result<TokenStream> {
Ok(tokens)
} else {
return Err(syn::Error::new_spanned(
Err(syn::Error::new_spanned(
ast,
"#[pyproto] can only be used with protocol trait implementations",
));
))
}
}

View file

@ -2,12 +2,24 @@
use proc_macro2::Span;
use proc_macro2::TokenStream;
use syn;
pub fn print_err(msg: String, t: TokenStream) {
println!("Error: {} in '{}'", msg, t.to_string());
}
/// Check if the given type `ty` is `pyo3::Python`.
pub fn if_type_is_python(ty: &syn::Type) -> bool {
match ty {
syn::Type::Path(ref typath) => typath
.path
.segments
.last()
.map(|seg| seg.ident == "Python")
.unwrap_or(false),
_ => false,
}
}
// FIXME(althonos): not sure the docstring formatting is on par here.
pub fn get_doc(attrs: &[syn::Attribute], null_terminated: bool) -> syn::Lit {
let mut doc = Vec::new();
@ -16,8 +28,8 @@ pub fn get_doc(attrs: &[syn::Attribute], null_terminated: bool) -> syn::Lit {
// let mut span = None;
for attr in attrs.iter() {
if let Some(syn::Meta::NameValue(ref metanv)) = attr.interpret_meta() {
if metanv.ident == "doc" {
if let Ok(syn::Meta::NameValue(ref metanv)) = attr.parse_meta() {
if metanv.path.is_ident("doc") {
// span = Some(metanv.span());
if let syn::Lit::Str(ref litstr) = metanv.lit {
let d = litstr.value();

View file

@ -14,7 +14,10 @@ edition = "2018"
proc-macro = true
[dependencies]
quote= "0.6.12"
proc-macro2 = "0.4.30"
syn = { version = "0.15.34", features = ["full", "extra-traits"] }
quote = "1"
proc-macro2 = "1"
syn = { version = "1", features = ["full", "extra-traits"] }
pyo3-derive-backend = { path = "../pyo3-derive-backend", version = "=0.7.0" }
[features]
unsound-subclass = ["pyo3-derive-backend/unsound-subclass"]

View file

@ -20,14 +20,14 @@ pub fn pymodule(attr: TokenStream, input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as syn::ItemFn);
let modname = if attr.is_empty() {
ast.ident.clone()
ast.sig.ident.clone()
} else {
parse_macro_input!(attr as syn::Ident)
};
process_functions_in_module(&mut ast);
let expanded = py_init(&ast.ident, &modname, get_doc(&ast.attrs, false));
let expanded = py_init(&ast.sig.ident, &modname, get_doc(&ast.attrs, false));
quote!(
#ast
@ -78,7 +78,7 @@ pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as syn::ItemFn);
let args = parse_macro_input!(attr as PyFunctionAttr);
let python_name = syn::Ident::new(&ast.ident.unraw().to_string(), Span::call_site());
let python_name = syn::Ident::new(&ast.sig.ident.unraw().to_string(), Span::call_site());
let expanded = add_fn_to_module(&ast, &python_name, args.arguments);
quote!(

View file

@ -26,11 +26,13 @@ use crate::Python;
use libc;
use std::ffi::CStr;
use std::os::raw;
use std::pin::Pin;
use std::{cell, mem, slice};
/// Allows access to the underlying buffer used by a python object such as `bytes`, `bytearray` or `array.array`.
// use Pin<Box> because Python expects that the Py_buffer struct has a stable memory address
#[repr(transparent)]
pub struct PyBuffer(Box<ffi::Py_buffer>); // use Box<> because Python expects that the Py_buffer struct has a stable memory address
pub struct PyBuffer(Pin<Box<ffi::Py_buffer>>);
// PyBuffer is thread-safe: the shape of the buffer is immutable while a Py_buffer exists.
// Accessing the buffer contents is protected using the GIL.
@ -165,7 +167,7 @@ impl PyBuffer {
/// Get the underlying buffer from the specified python object.
pub fn get(py: Python, obj: &PyAny) -> PyResult<PyBuffer> {
unsafe {
let mut buf = Box::new(mem::zeroed::<ffi::Py_buffer>());
let mut buf = Box::pin(mem::zeroed::<ffi::Py_buffer>());
err::error_on_minusone(
py,
ffi::PyObject_GetBuffer(obj.as_ptr(), &mut *buf, ffi::PyBUF_FULL_RO),
@ -704,6 +706,7 @@ mod test {
assert_eq!(buffer.to_vec::<u8>(py).unwrap(), b"abcde");
}
#[allow(clippy::float_cmp)] // The test wants to ensure that no precision was lost on the Python round-trip
#[test]
fn test_array_buffer() {
let gil = Python::acquire_gil();
@ -713,7 +716,7 @@ mod test {
.unwrap()
.call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None)
.unwrap();
let buffer = PyBuffer::get(py, array.into()).unwrap();
let buffer = PyBuffer::get(py, array).unwrap();
assert_eq!(buffer.dimensions(), 1);
assert_eq!(buffer.item_count(), 4);
assert_eq!(buffer.format().to_str().unwrap(), "f");

View file

@ -5,8 +5,8 @@
use crate::err::PyResult;
use crate::exceptions::OverflowError;
use crate::ffi::{self, Py_hash_t};
use crate::Python;
use crate::{IntoPyObject, IntoPyPointer};
use crate::IntoPyPointer;
use crate::{IntoPy, PyObject, Python};
use std::os::raw::c_int;
use std::{isize, ptr};
@ -21,12 +21,12 @@ pub struct PyObjectCallbackConverter;
impl<S> CallbackConverter<S> for PyObjectCallbackConverter
where
S: IntoPyObject,
S: IntoPy<PyObject>,
{
type R = *mut ffi::PyObject;
fn convert(val: S, py: Python) -> *mut ffi::PyObject {
val.into_object(py).into_ptr()
val.into_py(py).into_ptr()
}
#[inline]

View file

@ -11,14 +11,14 @@
use crate::callback::{BoolCallbackConverter, HashConverter, PyObjectCallbackConverter};
use crate::class::methods::PyMethodDef;
use crate::err::{PyErr, PyResult};
use crate::exceptions;
use crate::ffi;
use crate::objectprotocol::ObjectProtocol;
use crate::type_object::PyTypeInfo;
use crate::types::PyAny;
use crate::FromPyObject;
use crate::IntoPyPointer;
use crate::Python;
use crate::{FromPyObject, IntoPyObject};
use crate::{exceptions, IntoPy, PyObject};
use std::os::raw::c_int;
use std::ptr;
@ -109,7 +109,7 @@ pub trait PyObjectProtocol<'p>: PyTypeInfo {
pub trait PyObjectGetAttrProtocol<'p>: PyObjectProtocol<'p> {
type Name: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyObjectSetAttrProtocol<'p>: PyObjectProtocol<'p> {
@ -122,16 +122,16 @@ pub trait PyObjectDelAttrProtocol<'p>: PyObjectProtocol<'p> {
type Result: Into<PyResult<()>>;
}
pub trait PyObjectStrProtocol<'p>: PyObjectProtocol<'p> {
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyObjectReprProtocol<'p>: PyObjectProtocol<'p> {
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyObjectFormatProtocol<'p>: PyObjectProtocol<'p> {
type Format: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyObjectHashProtocol<'p>: PyObjectProtocol<'p> {
@ -141,12 +141,12 @@ pub trait PyObjectBoolProtocol<'p>: PyObjectProtocol<'p> {
type Result: Into<PyResult<bool>>;
}
pub trait PyObjectBytesProtocol<'p>: PyObjectProtocol<'p> {
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyObjectRichcmpProtocol<'p>: PyObjectProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
@ -207,12 +207,37 @@ where
T: for<'p> PyObjectGetAttrProtocol<'p>,
{
fn tp_getattro() -> Option<ffi::binaryfunc> {
py_binary_func!(
PyObjectGetAttrProtocol,
T::__getattr__,
T::Success,
PyObjectCallbackConverter
)
#[allow(unused_mut)]
unsafe extern "C" fn wrap<T>(
slf: *mut ffi::PyObject,
arg: *mut ffi::PyObject,
) -> *mut ffi::PyObject
where
T: for<'p> PyObjectGetAttrProtocol<'p>,
{
let _pool = crate::GILPool::new();
let py = Python::assume_gil_acquired();
// Behave like python's __getattr__ (as opposed to __getattribute__) and check
// for existing fields and methods first
let existing = ffi::PyObject_GenericGetAttr(slf, arg);
if existing.is_null() {
// PyObject_HasAttr also tries to get an object and clears the error if it fails
ffi::PyErr_Clear();
} else {
return existing;
}
let slf = py.mut_from_borrowed_ptr::<T>(slf);
let arg = py.from_borrowed_ptr::<crate::types::PyAny>(arg);
let result = match arg.extract() {
Ok(arg) => slf.__getattr__(arg).into(),
Err(e) => Err(e),
};
crate::callback::cb_convert(PyObjectCallbackConverter, py, result)
}
Some(wrap::<T>)
}
}
@ -438,7 +463,7 @@ where
Err(e) => Err(e),
};
match res {
Ok(val) => val.into_object(py).into_ptr(),
Ok(val) => val.into_py(py).into_ptr(),
Err(e) => {
e.restore(py);
ptr::null_mut()

View file

@ -7,6 +7,7 @@
use crate::class::methods::PyMethodDef;
use crate::err::PyResult;
use crate::type_object::PyTypeInfo;
use crate::PyObject;
/// Context manager interface
#[allow(unused_variables)]
@ -32,7 +33,7 @@ pub trait PyContextProtocol<'p>: PyTypeInfo {
}
pub trait PyContextEnterProtocol<'p>: PyContextProtocol<'p> {
type Success: crate::IntoPyObject;
type Success: crate::IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
@ -40,7 +41,7 @@ pub trait PyContextExitProtocol<'p>: PyContextProtocol<'p> {
type ExcType: crate::FromPyObject<'p>;
type ExcValue: crate::FromPyObject<'p>;
type Traceback: crate::FromPyObject<'p>;
type Success: crate::IntoPyObject;
type Success: crate::IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}

View file

@ -8,10 +8,10 @@
use crate::callback::{PyObjectCallbackConverter, UnitCallbackConverter};
use crate::class::methods::PyMethodDef;
use crate::err::PyResult;
use crate::ffi;
use crate::type_object::PyTypeInfo;
use crate::types::{PyAny, PyType};
use crate::{FromPyObject, IntoPyObject};
use crate::FromPyObject;
use crate::{ffi, IntoPy, PyObject};
use std::os::raw::c_int;
/// Descriptor interface
@ -49,7 +49,7 @@ pub trait PyDescrProtocol<'p>: PyTypeInfo {
pub trait PyDescrGetProtocol<'p>: PyDescrProtocol<'p> {
type Inst: FromPyObject<'p>;
type Owner: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}

View file

@ -13,15 +13,9 @@ use std::os::raw::{c_int, c_void};
pub struct PyTraverseError(c_int);
/// GC support
#[allow(unused_variables)]
pub trait PyGCProtocol<'p>: PyTypeInfo {
fn __traverse__(&'p self, visit: PyVisit) -> Result<(), PyTraverseError> {
unimplemented!()
}
fn __clear__(&'p mut self) {
unimplemented!()
}
fn __traverse__(&'p self, visit: PyVisit) -> Result<(), PyTraverseError>;
fn __clear__(&'p mut self);
}
pub trait PyGCTraverseProtocol<'p>: PyGCProtocol<'p> {}

View file

@ -4,12 +4,11 @@
use crate::callback::{CallbackConverter, PyObjectCallbackConverter};
use crate::err::PyResult;
use crate::ffi;
use crate::instance::PyRefMut;
use crate::type_object::PyTypeInfo;
use crate::IntoPyObject;
use crate::IntoPyPointer;
use crate::Python;
use crate::{ffi, IntoPy, PyObject};
use std::ptr;
/// Python Iterator Interface.
@ -34,12 +33,12 @@ pub trait PyIterProtocol<'p>: PyTypeInfo + Sized {
}
pub trait PyIterIterProtocol<'p>: PyIterProtocol<'p> {
type Success: crate::IntoPyObject;
type Success: crate::IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyIterNextProtocol<'p>: PyIterProtocol<'p> {
type Success: crate::IntoPyObject;
type Success: crate::IntoPy<PyObject>;
type Result: Into<PyResult<Option<Self::Success>>>;
}
@ -111,13 +110,13 @@ struct IterNextConverter;
impl<T> CallbackConverter<Option<T>> for IterNextConverter
where
T: IntoPyObject,
T: IntoPy<PyObject>,
{
type R = *mut ffi::PyObject;
fn convert(val: Option<T>, py: Python) -> *mut ffi::PyObject {
match val {
Some(val) => val.into_object(py).into_ptr(),
Some(val) => val.into_py(py).into_ptr(),
None => unsafe {
ffi::PyErr_SetNone(ffi::PyExc_StopIteration);
ptr::null_mut()

View file

@ -21,7 +21,7 @@ macro_rules! py_unary_func {
let py = $crate::Python::assume_gil_acquired();
let slf = py.mut_from_borrowed_ptr::<T>(slf);
let res = slf.$f().into();
$crate::callback::cb_convert($conv, py, res)
$crate::callback::cb_convert($conv, py, res.map(|x| x))
}
Some(wrap::<$class>)
}};

View file

@ -6,11 +6,11 @@
use crate::callback::{LenResultConverter, PyObjectCallbackConverter};
use crate::class::methods::PyMethodDef;
use crate::err::{PyErr, PyResult};
use crate::exceptions;
use crate::ffi;
use crate::type_object::PyTypeInfo;
use crate::FromPyObject;
use crate::Python;
use crate::{FromPyObject, IntoPyObject};
use crate::{exceptions, IntoPy, PyObject};
/// Mapping interface
#[allow(unused_variables)]
@ -74,7 +74,7 @@ pub trait PyMappingLenProtocol<'p>: PyMappingProtocol<'p> {
pub trait PyMappingGetItemProtocol<'p>: PyMappingProtocol<'p> {
type Key: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
@ -90,7 +90,7 @@ pub trait PyMappingDelItemProtocol<'p>: PyMappingProtocol<'p> {
}
pub trait PyMappingIterProtocol<'p>: PyMappingProtocol<'p> {
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
@ -100,7 +100,7 @@ pub trait PyMappingContainsProtocol<'p>: PyMappingProtocol<'p> {
}
pub trait PyMappingReversedProtocol<'p>: PyMappingProtocol<'p> {
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}

View file

@ -7,9 +7,9 @@ use crate::callback::PyObjectCallbackConverter;
use crate::class::basic::PyObjectProtocolImpl;
use crate::class::methods::PyMethodDef;
use crate::err::PyResult;
use crate::ffi;
use crate::type_object::PyTypeInfo;
use crate::{FromPyObject, IntoPyObject};
use crate::FromPyObject;
use crate::{ffi, IntoPy, PyObject};
/// Number interface
#[allow(unused_variables)]
@ -323,56 +323,56 @@ pub trait PyNumberProtocol<'p>: PyTypeInfo {
pub trait PyNumberAddProtocol<'p>: PyNumberProtocol<'p> {
type Left: FromPyObject<'p>;
type Right: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberSubProtocol<'p>: PyNumberProtocol<'p> {
type Left: FromPyObject<'p>;
type Right: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberMulProtocol<'p>: PyNumberProtocol<'p> {
type Left: FromPyObject<'p>;
type Right: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberMatmulProtocol<'p>: PyNumberProtocol<'p> {
type Left: FromPyObject<'p>;
type Right: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberTruedivProtocol<'p>: PyNumberProtocol<'p> {
type Left: FromPyObject<'p>;
type Right: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberFloordivProtocol<'p>: PyNumberProtocol<'p> {
type Left: FromPyObject<'p>;
type Right: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberModProtocol<'p>: PyNumberProtocol<'p> {
type Left: FromPyObject<'p>;
type Right: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberDivmodProtocol<'p>: PyNumberProtocol<'p> {
type Left: FromPyObject<'p>;
type Right: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
@ -380,127 +380,127 @@ pub trait PyNumberPowProtocol<'p>: PyNumberProtocol<'p> {
type Left: FromPyObject<'p>;
type Right: FromPyObject<'p>;
type Modulo: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberLShiftProtocol<'p>: PyNumberProtocol<'p> {
type Left: FromPyObject<'p>;
type Right: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRShiftProtocol<'p>: PyNumberProtocol<'p> {
type Left: FromPyObject<'p>;
type Right: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberAndProtocol<'p>: PyNumberProtocol<'p> {
type Left: FromPyObject<'p>;
type Right: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberXorProtocol<'p>: PyNumberProtocol<'p> {
type Left: FromPyObject<'p>;
type Right: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberOrProtocol<'p>: PyNumberProtocol<'p> {
type Left: FromPyObject<'p>;
type Right: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRAddProtocol<'p>: PyNumberProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRSubProtocol<'p>: PyNumberProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRMulProtocol<'p>: PyNumberProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRMatmulProtocol<'p>: PyNumberProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRTruedivProtocol<'p>: PyNumberProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRFloordivProtocol<'p>: PyNumberProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRModProtocol<'p>: PyNumberProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRDivmodProtocol<'p>: PyNumberProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRPowProtocol<'p>: PyNumberProtocol<'p> {
type Other: FromPyObject<'p>;
type Modulo: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRLShiftProtocol<'p>: PyNumberProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRRShiftProtocol<'p>: PyNumberProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRAndProtocol<'p>: PyNumberProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRXorProtocol<'p>: PyNumberProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberROrProtocol<'p>: PyNumberProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
@ -576,47 +576,47 @@ pub trait PyNumberIOrProtocol<'p>: PyNumberProtocol<'p> {
}
pub trait PyNumberNegProtocol<'p>: PyNumberProtocol<'p> {
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberPosProtocol<'p>: PyNumberProtocol<'p> {
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberAbsProtocol<'p>: PyNumberProtocol<'p> {
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberInvertProtocol<'p>: PyNumberProtocol<'p> {
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberComplexProtocol<'p>: PyNumberProtocol<'p> {
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberIntProtocol<'p>: PyNumberProtocol<'p> {
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberFloatProtocol<'p>: PyNumberProtocol<'p> {
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberRoundProtocol<'p>: PyNumberProtocol<'p> {
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyNumberIndexProtocol<'p>: PyNumberProtocol<'p> {
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
@ -1271,7 +1271,8 @@ where
}
}
trait PyNumberRAddProtocolImpl {
#[doc(hidden)]
pub trait PyNumberRAddProtocolImpl {
fn __radd__() -> Option<PyMethodDef> {
None
}
@ -1279,7 +1280,8 @@ trait PyNumberRAddProtocolImpl {
impl<'p, T> PyNumberRAddProtocolImpl for T where T: PyNumberProtocol<'p> {}
trait PyNumberRSubProtocolImpl {
#[doc(hidden)]
pub trait PyNumberRSubProtocolImpl {
fn __rsub__() -> Option<PyMethodDef> {
None
}
@ -1287,7 +1289,8 @@ trait PyNumberRSubProtocolImpl {
impl<'p, T> PyNumberRSubProtocolImpl for T where T: PyNumberProtocol<'p> {}
trait PyNumberRMulProtocolImpl {
#[doc(hidden)]
pub trait PyNumberRMulProtocolImpl {
fn __rmul__() -> Option<PyMethodDef> {
None
}
@ -1295,7 +1298,8 @@ trait PyNumberRMulProtocolImpl {
impl<'p, T> PyNumberRMulProtocolImpl for T where T: PyNumberProtocol<'p> {}
trait PyNumberRMatmulProtocolImpl {
#[doc(hidden)]
pub trait PyNumberRMatmulProtocolImpl {
fn __rmatmul__() -> Option<PyMethodDef> {
None
}
@ -1303,7 +1307,8 @@ trait PyNumberRMatmulProtocolImpl {
impl<'p, T> PyNumberRMatmulProtocolImpl for T where T: PyNumberProtocol<'p> {}
trait PyNumberRTruedivProtocolImpl {
#[doc(hidden)]
pub trait PyNumberRTruedivProtocolImpl {
fn __rtruediv__() -> Option<PyMethodDef> {
None
}
@ -1311,7 +1316,8 @@ trait PyNumberRTruedivProtocolImpl {
impl<'p, T> PyNumberRTruedivProtocolImpl for T where T: PyNumberProtocol<'p> {}
trait PyNumberRFloordivProtocolImpl {
#[doc(hidden)]
pub trait PyNumberRFloordivProtocolImpl {
fn __rfloordiv__() -> Option<PyMethodDef> {
None
}
@ -1319,7 +1325,8 @@ trait PyNumberRFloordivProtocolImpl {
impl<'p, T> PyNumberRFloordivProtocolImpl for T where T: PyNumberProtocol<'p> {}
trait PyNumberRModProtocolImpl {
#[doc(hidden)]
pub trait PyNumberRModProtocolImpl {
fn __rmod__() -> Option<PyMethodDef> {
None
}
@ -1327,7 +1334,8 @@ trait PyNumberRModProtocolImpl {
impl<'p, T> PyNumberRModProtocolImpl for T where T: PyNumberProtocol<'p> {}
trait PyNumberRDivmodProtocolImpl {
#[doc(hidden)]
pub trait PyNumberRDivmodProtocolImpl {
fn __rdivmod__() -> Option<PyMethodDef> {
None
}
@ -1335,7 +1343,8 @@ trait PyNumberRDivmodProtocolImpl {
impl<'p, T> PyNumberRDivmodProtocolImpl for T where T: PyNumberProtocol<'p> {}
trait PyNumberRPowProtocolImpl {
#[doc(hidden)]
pub trait PyNumberRPowProtocolImpl {
fn __rpow__() -> Option<PyMethodDef> {
None
}
@ -1343,7 +1352,8 @@ trait PyNumberRPowProtocolImpl {
impl<'p, T> PyNumberRPowProtocolImpl for T where T: PyNumberProtocol<'p> {}
trait PyNumberRLShiftProtocolImpl {
#[doc(hidden)]
pub trait PyNumberRLShiftProtocolImpl {
fn __rlshift__() -> Option<PyMethodDef> {
None
}
@ -1351,7 +1361,8 @@ trait PyNumberRLShiftProtocolImpl {
impl<'p, T> PyNumberRLShiftProtocolImpl for T where T: PyNumberProtocol<'p> {}
trait PyNumberRRShiftProtocolImpl {
#[doc(hidden)]
pub trait PyNumberRRShiftProtocolImpl {
fn __rrshift__() -> Option<PyMethodDef> {
None
}
@ -1359,7 +1370,8 @@ trait PyNumberRRShiftProtocolImpl {
impl<'p, T> PyNumberRRShiftProtocolImpl for T where T: PyNumberProtocol<'p> {}
trait PyNumberRAndProtocolImpl {
#[doc(hidden)]
pub trait PyNumberRAndProtocolImpl {
fn __rand__() -> Option<PyMethodDef> {
None
}
@ -1367,7 +1379,8 @@ trait PyNumberRAndProtocolImpl {
impl<'p, T> PyNumberRAndProtocolImpl for T where T: PyNumberProtocol<'p> {}
trait PyNumberRXorProtocolImpl {
#[doc(hidden)]
pub trait PyNumberRXorProtocolImpl {
fn __rxor__() -> Option<PyMethodDef> {
None
}
@ -1375,7 +1388,8 @@ trait PyNumberRXorProtocolImpl {
impl<'p, T> PyNumberRXorProtocolImpl for T where T: PyNumberProtocol<'p> {}
trait PyNumberROrProtocolImpl {
#[doc(hidden)]
pub trait PyNumberROrProtocolImpl {
fn __ror__() -> Option<PyMethodDef> {
None
}

View file

@ -13,6 +13,7 @@ use crate::class::methods::PyMethodDef;
use crate::err::PyResult;
use crate::ffi;
use crate::type_object::PyTypeInfo;
use crate::PyObject;
/// Python Async/Await support interface.
///
@ -61,22 +62,22 @@ pub trait PyAsyncProtocol<'p>: PyTypeInfo {
}
pub trait PyAsyncAwaitProtocol<'p>: PyAsyncProtocol<'p> {
type Success: crate::IntoPyObject;
type Success: crate::IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyAsyncAiterProtocol<'p>: PyAsyncProtocol<'p> {
type Success: crate::IntoPyObject;
type Success: crate::IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PyAsyncAnextProtocol<'p>: PyAsyncProtocol<'p> {
type Success: crate::IntoPyObject;
type Success: crate::IntoPy<PyObject>;
type Result: Into<PyResult<Option<Self::Success>>>;
}
pub trait PyAsyncAenterProtocol<'p>: PyAsyncProtocol<'p> {
type Success: crate::IntoPyObject;
type Success: crate::IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
@ -84,7 +85,7 @@ pub trait PyAsyncAexitProtocol<'p>: PyAsyncProtocol<'p> {
type ExcType: crate::FromPyObject<'p>;
type ExcValue: crate::FromPyObject<'p>;
type Traceback: crate::FromPyObject<'p>;
type Success: crate::IntoPyObject;
type Success: crate::IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
@ -186,23 +187,22 @@ impl<'p, T> PyAsyncAnextProtocolImpl for T where T: PyAsyncProtocol<'p> {}
mod anext {
use super::{PyAsyncAnextProtocol, PyAsyncAnextProtocolImpl};
use crate::callback::CallbackConverter;
use crate::ffi;
use crate::IntoPyObject;
use crate::IntoPyPointer;
use crate::Python;
use crate::{ffi, IntoPy, PyObject};
use std::ptr;
pub struct IterANextResultConverter;
impl<T> CallbackConverter<Option<T>> for IterANextResultConverter
where
T: IntoPyObject,
T: IntoPy<PyObject>,
{
type R = *mut ffi::PyObject;
fn convert(val: Option<T>, py: Python) -> *mut ffi::PyObject {
match val {
Some(val) => val.into_object(py).into_ptr(),
Some(val) => val.into_py(py).into_ptr(),
None => unsafe {
ffi::PyErr_SetNone(ffi::PyExc_StopAsyncIteration);
ptr::null_mut()

View file

@ -5,13 +5,13 @@
use crate::callback::{BoolCallbackConverter, LenResultConverter, PyObjectCallbackConverter};
use crate::err::{PyErr, PyResult};
use crate::exceptions;
use crate::ffi;
use crate::objectprotocol::ObjectProtocol;
use crate::type_object::PyTypeInfo;
use crate::types::PyAny;
use crate::FromPyObject;
use crate::Python;
use crate::{FromPyObject, IntoPyObject};
use crate::{exceptions, IntoPy, PyObject};
use std::os::raw::c_int;
/// Sequence interface
@ -90,7 +90,7 @@ pub trait PySequenceLenProtocol<'p>: PySequenceProtocol<'p> {
pub trait PySequenceGetItemProtocol<'p>: PySequenceProtocol<'p> {
type Index: FromPyObject<'p> + From<isize>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
@ -112,22 +112,22 @@ pub trait PySequenceContainsProtocol<'p>: PySequenceProtocol<'p> {
pub trait PySequenceConcatProtocol<'p>: PySequenceProtocol<'p> {
type Other: FromPyObject<'p>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PySequenceRepeatProtocol<'p>: PySequenceProtocol<'p> {
type Index: FromPyObject<'p> + From<isize>;
type Success: IntoPyObject;
type Success: IntoPy<PyObject>;
type Result: Into<PyResult<Self::Success>>;
}
pub trait PySequenceInplaceConcatProtocol<'p>: PySequenceProtocol<'p> + IntoPyObject {
pub trait PySequenceInplaceConcatProtocol<'p>: PySequenceProtocol<'p> + IntoPy<PyObject> {
type Other: FromPyObject<'p>;
type Result: Into<PyResult<Self>>;
}
pub trait PySequenceInplaceRepeatProtocol<'p>: PySequenceProtocol<'p> + IntoPyObject {
pub trait PySequenceInplaceRepeatProtocol<'p>: PySequenceProtocol<'p> + IntoPy<PyObject> {
type Index: FromPyObject<'p> + From<isize>;
type Result: Into<PyResult<Self>>;
}

View file

@ -139,7 +139,7 @@ impl<T, U> IntoPy<U> for T
where
U: FromPy<T>,
{
fn into_py(self, py: Python) -> U {
default fn into_py(self, py: Python) -> U {
U::from_py(self, py)
}
}
@ -151,13 +151,6 @@ impl<T> FromPy<T> for T {
}
}
/// Conversion trait that allows various objects to be converted into `PyObject`
/// by consuming original object.
pub trait IntoPyObject {
/// Converts self into a Python object. (Consumes self)
fn into_object(self, py: Python) -> PyObject;
}
/// `FromPyObject` is implemented by various types that can be extracted from
/// a Python object reference.
///
@ -209,13 +202,13 @@ where
}
}
impl<T> IntoPyObject for Option<T>
impl<T> IntoPy<PyObject> for Option<T>
where
T: IntoPyObject,
T: IntoPy<PyObject>,
{
fn into_object(self, py: Python) -> PyObject {
fn into_py(self, py: Python) -> PyObject {
match self {
Some(val) => val.into_object(py),
Some(val) => val.into_py(py),
None => py.None(),
}
}
@ -228,29 +221,19 @@ impl ToPyObject for () {
}
}
impl IntoPyObject for () {
fn into_object(self, py: Python) -> PyObject {
impl FromPy<()> for PyObject {
fn from_py(_: (), py: Python) -> Self {
py.None()
}
}
impl<'a, T> IntoPyObject for &'a T
impl<'a, T> FromPy<&'a T> for PyObject
where
T: AsPyPointer,
{
#[inline]
fn into_object(self, py: Python) -> PyObject {
unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) }
}
}
impl<'a, T> IntoPyObject for &'a mut T
where
T: AsPyPointer,
{
#[inline]
fn into_object(self, py: Python) -> PyObject {
unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) }
fn from_py(other: &'a T, py: Python) -> PyObject {
unsafe { PyObject::from_borrowed_ptr(py, other.as_ptr()) }
}
}

View file

@ -6,12 +6,11 @@
use crate::err::PyResult;
use crate::exceptions::TypeError;
use crate::ffi;
use crate::init_once;
use crate::types::{PyAny, PyDict, PyModule, PyTuple};
use crate::GILPool;
use crate::IntoPyObject;
use crate::Python;
use crate::{ffi, IntoPy, PyObject};
use std::ptr;
/// Description of a python parameter; used for `parse_args()`.
@ -154,7 +153,7 @@ pub unsafe fn make_module(
}
}
/// This trait wraps a T: IntoPyObject into PyResult<T> while PyResult<T> remains PyResult<T>.
/// This trait wraps a T: IntoPy<PyObject> into PyResult<T> while PyResult<T> remains PyResult<T>.
///
/// This is necessary because proc macros run before typechecking and can't decide
/// whether a return type is a (possibly aliased) PyResult or not. It is also quite handy because
@ -163,13 +162,13 @@ pub trait IntoPyResult<T> {
fn into_py_result(self) -> PyResult<T>;
}
impl<T: IntoPyObject> IntoPyResult<T> for T {
impl<T: IntoPy<PyObject>> IntoPyResult<T> for T {
fn into_py_result(self) -> PyResult<T> {
Ok(self)
}
}
impl<T: IntoPyObject> IntoPyResult<T> for PyResult<T> {
impl<T: IntoPy<PyObject>> IntoPyResult<T> for PyResult<T> {
fn into_py_result(self) -> PyResult<T> {
self
}

View file

@ -1,7 +1,5 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::exceptions;
use crate::ffi;
use crate::instance::Py;
use crate::object::PyObject;
use crate::type_object::PyTypeObject;
@ -9,9 +7,10 @@ use crate::types::{PyAny, PyType};
use crate::AsPyPointer;
use crate::IntoPyPointer;
use crate::Python;
use crate::{IntoPyObject, ToBorrowedObject, ToPyObject};
use crate::{exceptions, IntoPy};
use crate::{ffi, FromPy};
use crate::{ToBorrowedObject, ToPyObject};
use libc::c_int;
use std::error::Error;
use std::ffi::CString;
use std::io;
use std::os::raw::c_char;
@ -358,9 +357,9 @@ impl std::fmt::Debug for PyErr {
}
}
impl IntoPyObject for PyErr {
fn into_object(self, py: Python) -> PyObject {
self.instance(py)
impl FromPy<PyErr> for PyObject {
fn from_py(other: PyErr, py: Python) -> Self {
other.instance(py)
}
}
@ -371,8 +370,8 @@ impl ToPyObject for PyErr {
}
}
impl<'a> IntoPyObject for &'a PyErr {
fn into_object(self, py: Python) -> PyObject {
impl<'a> IntoPy<PyObject> for &'a PyErr {
fn into_py(self, py: Python) -> PyObject {
let err = self.clone_ref(py);
err.instance(py)
}
@ -412,7 +411,7 @@ macro_rules! impl_to_pyerr {
($err: ty, $pyexc: ty) => {
impl PyErrArguments for $err {
fn arguments(&self, py: Python) -> PyObject {
self.description().to_object(py)
self.to_string().to_object(py)
}
}
@ -459,10 +458,9 @@ impl std::convert::From<io::Error> for PyErr {
}
}
/// Extract `errno` and `errdesc` from from `io::Error`
impl PyErrArguments for io::Error {
fn arguments(&self, py: Python) -> PyObject {
(self.raw_os_error().unwrap_or(0), self.description()).to_object(py)
self.to_string().to_object(py)
}
}
@ -474,7 +472,7 @@ impl<W: 'static + Send + std::fmt::Debug> std::convert::From<std::io::IntoInnerE
impl<W: Send + std::fmt::Debug> PyErrArguments for std::io::IntoInnerError<W> {
fn arguments(&self, py: Python) -> PyObject {
self.description().to_object(py)
self.to_string().to_object(py)
}
}

View file

@ -299,7 +299,7 @@ mod typeobject {
impl Default for PyNumberMethods {
#[inline]
fn default() -> Self {
unsafe { mem::zeroed() }
PyNumberMethods_INIT
}
}
macro_rules! as_expr {

View file

@ -9,8 +9,8 @@ use spin;
use std::ptr::NonNull;
use std::{any, marker, rc, sync};
static START: sync::Once = sync::ONCE_INIT;
static START_PYO3: sync::Once = sync::ONCE_INIT;
static START: sync::Once = sync::Once::new();
static START_PYO3: sync::Once = sync::Once::new();
/// Prepares the use of Python in a free-threaded context.
///
@ -280,18 +280,16 @@ use self::array_list::ArrayList;
mod array_list {
use std::collections::LinkedList;
use std::mem;
const BLOCK_SIZE: usize = 256;
/// A container type for Release Pool
/// See #271 for why this is crated
pub(super) struct ArrayList<T> {
inner: LinkedList<[T; BLOCK_SIZE]>,
inner: LinkedList<[Option<T>; BLOCK_SIZE]>,
length: usize,
}
impl<T: Clone> ArrayList<T> {
impl<T: Copy> ArrayList<T> {
pub fn new() -> Self {
ArrayList {
inner: LinkedList::new(),
@ -301,20 +299,20 @@ mod array_list {
pub fn push_back(&mut self, item: T) -> &T {
let next_idx = self.next_idx();
if next_idx == 0 {
self.inner.push_back(unsafe { mem::uninitialized() });
self.inner.push_back([None; BLOCK_SIZE]);
}
self.inner.back_mut().unwrap()[next_idx] = item;
self.inner.back_mut().unwrap()[next_idx] = Some(item);
self.length += 1;
&self.inner.back().unwrap()[next_idx]
self.inner.back().unwrap()[next_idx].as_ref().unwrap()
}
pub fn pop_back(&mut self) -> Option<T> {
self.length -= 1;
let current_idx = self.next_idx();
if current_idx == 0 {
let last_list = self.inner.pop_back()?;
return Some(last_list[0].clone());
return last_list[0];
}
self.inner.back().map(|arr| arr[current_idx].clone())
self.inner.back().and_then(|arr| arr[current_idx])
}
pub fn len(&self) -> usize {
self.length

View file

@ -1,6 +1,5 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::err::{PyErr, PyResult};
use crate::ffi;
use crate::gil;
use crate::instance;
use crate::object::PyObject;
@ -8,9 +7,8 @@ use crate::objectprotocol::ObjectProtocol;
use crate::type_object::PyTypeCreate;
use crate::type_object::{PyTypeInfo, PyTypeObject};
use crate::types::PyAny;
use crate::{
AsPyPointer, FromPyObject, FromPyPointer, IntoPyObject, IntoPyPointer, Python, ToPyObject,
};
use crate::{ffi, IntoPy};
use crate::{AsPyPointer, FromPyObject, FromPyPointer, IntoPyPointer, Python, ToPyObject};
use std::marker::PhantomData;
use std::mem;
use std::ops::{Deref, DerefMut};
@ -96,6 +94,12 @@ impl<'a, T: PyTypeInfo> ToPyObject for PyRef<'a, T> {
}
}
impl<'a, T: PyTypeInfo> IntoPy<PyObject> for PyRef<'a, T> {
fn into_py(self, py: Python) -> PyObject {
self.to_object(py)
}
}
impl<'a, T: PyTypeInfo> Deref for PyRef<'a, T> {
type Target = T;
fn deref(&self) -> &T {
@ -170,6 +174,12 @@ impl<'a, T: PyTypeInfo> ToPyObject for PyRefMut<'a, T> {
}
}
impl<'a, T: PyTypeInfo> IntoPy<PyObject> for PyRefMut<'a, T> {
fn into_py(self, py: Python) -> PyObject {
self.to_object(py)
}
}
impl<'a, T: PyTypeInfo> Deref for PyRefMut<'a, T> {
type Target = T;
fn deref(&self) -> &T {
@ -395,11 +405,11 @@ impl<T> ToPyObject for Py<T> {
}
}
impl<T> IntoPyObject for Py<T> {
impl<T> IntoPy<PyObject> for Py<T> {
/// Converts `Py` instance -> PyObject.
/// Consumes `self` without calling `Py_DECREF()`
#[inline]
fn into_object(self, py: Python) -> PyObject {
fn into_py(self, py: Python) -> PyObject {
unsafe { PyObject::from_owned_ptr(py, self.into_ptr()) }
}
}

30
src/lib.rs Normal file → Executable file
View file

@ -119,8 +119,8 @@
pub use crate::class::*;
pub use crate::conversion::{
AsPyPointer, FromPy, FromPyObject, FromPyPointer, IntoPy, IntoPyObject, IntoPyPointer,
PyTryFrom, PyTryInto, ToBorrowedObject, ToPyObject,
AsPyPointer, FromPy, FromPyObject, FromPyPointer, IntoPy, IntoPyPointer, PyTryFrom, PyTryInto,
ToBorrowedObject, ToPyObject,
};
pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyErrValue, PyResult};
pub use crate::gil::{init_once, GILGuard, GILPool};
@ -132,7 +132,7 @@ pub use crate::type_object::{PyObjectAlloc, PyRawObject, PyTypeInfo};
// Re-exported for wrap_function
#[doc(hidden)]
pub use mashup;
pub use paste;
// Re-exported for py_run
#[doc(hidden)]
pub use indoc;
@ -183,18 +183,8 @@ pub mod proc_macro {
/// Use this together with `#[pyfunction]` and [types::PyModule::add_wrapped].
#[macro_export]
macro_rules! wrap_pyfunction {
($function_name:ident) => {{
// Get the mashup macro and its helpers into scope
use pyo3::mashup::*;
mashup! {
// Make sure this ident matches the one in function_wrapper_ident
m["method"] = __pyo3_get_function_ $function_name;
}
m! {
&"method"
}
($function_name: ident) => {{
&pyo3::paste::expr! { [<__pyo3_get_function_ $function_name>] }
}};
}
@ -204,14 +194,8 @@ macro_rules! wrap_pyfunction {
#[macro_export]
macro_rules! wrap_pymodule {
($module_name:ident) => {{
use pyo3::mashup::*;
mashup! {
m["method"] = PyInit_ $module_name;
}
m! {
&|py| unsafe { pyo3::PyObject::from_owned_ptr(py, "method"()) }
pyo3::paste::expr! {
&|py| unsafe { pyo3::PyObject::from_owned_ptr(py, [<PyInit_ $module_name>]()) }
}
}};
}

View file

@ -8,9 +8,7 @@ use crate::types::{PyAny, PyDict, PyTuple};
use crate::AsPyPointer;
use crate::Py;
use crate::Python;
use crate::{
FromPyObject, IntoPy, IntoPyObject, IntoPyPointer, PyTryFrom, ToBorrowedObject, ToPyObject,
};
use crate::{FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, ToBorrowedObject, ToPyObject};
use std::ptr::NonNull;
/// A python object
@ -299,13 +297,6 @@ impl PartialEq for PyObject {
}
}
impl IntoPyObject for PyObject {
#[inline]
fn into_object(self, _py: Python) -> PyObject {
self
}
}
impl<'a> FromPyObject<'a> for PyObject {
#[inline]
/// Extracts `Self` from the source `PyObject`.

View file

@ -16,9 +16,7 @@ pub use crate::instance::{AsPyRef, Py, PyRef, PyRefMut};
pub use crate::object::PyObject;
pub use crate::objectprotocol::ObjectProtocol;
pub use crate::python::Python;
pub use crate::{
FromPy, FromPyObject, IntoPy, IntoPyObject, IntoPyPointer, PyTryFrom, PyTryInto, ToPyObject,
};
pub use crate::{FromPy, FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, PyTryInto, ToPyObject};
// This is only part of the prelude because we need it for the pymodule function
pub use crate::types::PyModule;
// This is required for the constructor

View file

@ -234,6 +234,7 @@ impl<'p> Python<'p> {
}
/// Register `ffi::PyObject` pointer in release pool
#[allow(clippy::wrong_self_convention)]
pub unsafe fn from_borrowed_ptr_to_obj(self, ptr: *mut ffi::PyObject) -> &'p PyAny {
match NonNull::new(ptr) {
Some(p) => gil::register_borrowed(self, p),
@ -243,6 +244,7 @@ impl<'p> Python<'p> {
/// Register `ffi::PyObject` pointer in release pool,
/// and do unchecked downcast to specific type.
#[allow(clippy::wrong_self_convention)]
pub unsafe fn from_owned_ptr<T>(self, ptr: *mut ffi::PyObject) -> &'p T
where
T: PyTypeInfo,
@ -262,6 +264,7 @@ impl<'p> Python<'p> {
/// Register owned `ffi::PyObject` pointer in release pool.
/// Returns `Err(PyErr)` if the pointer is `null`.
/// do unchecked downcast to specific type.
#[allow(clippy::wrong_self_convention)]
pub unsafe fn from_owned_ptr_or_err<T>(self, ptr: *mut ffi::PyObject) -> PyResult<&'p T>
where
T: PyTypeInfo,
@ -272,6 +275,7 @@ impl<'p> Python<'p> {
/// Register owned `ffi::PyObject` pointer in release pool.
/// Returns `None` if the pointer is `null`.
/// do unchecked downcast to specific type.
#[allow(clippy::wrong_self_convention)]
pub unsafe fn from_owned_ptr_or_opt<T>(self, ptr: *mut ffi::PyObject) -> Option<&'p T>
where
T: PyTypeInfo,
@ -282,6 +286,7 @@ impl<'p> Python<'p> {
/// Register borrowed `ffi::PyObject` pointer in release pool.
/// Panics if the pointer is `null`.
/// do unchecked downcast to specific type.
#[allow(clippy::wrong_self_convention)]
pub unsafe fn from_borrowed_ptr<T>(self, ptr: *mut ffi::PyObject) -> &'p T
where
T: PyTypeInfo,
@ -302,6 +307,7 @@ impl<'p> Python<'p> {
/// Register borrowed `ffi::PyObject` pointer in release pool.
/// Returns `Err(PyErr)` if the pointer is `null`.
/// do unchecked downcast to specific type.
#[allow(clippy::wrong_self_convention)]
pub unsafe fn from_borrowed_ptr_or_err<T>(self, ptr: *mut ffi::PyObject) -> PyResult<&'p T>
where
T: PyTypeInfo,
@ -312,6 +318,7 @@ impl<'p> Python<'p> {
/// Register borrowed `ffi::PyObject` pointer in release pool.
/// Returns `None` if the pointer is `null`.
/// do unchecked downcast to specific `T`.
#[allow(clippy::wrong_self_convention)]
pub unsafe fn from_borrowed_ptr_or_opt<T>(self, ptr: *mut ffi::PyObject) -> Option<&'p T>
where
T: PyTypeInfo,

View file

@ -352,36 +352,24 @@ where
// basic methods
<T as class::basic::PyObjectProtocolImpl>::tp_as_object(type_object);
fn to_ptr<T>(value: Option<T>) -> *mut T {
value
.map(|v| Box::into_raw(Box::new(v)))
.unwrap_or_else(ptr::null_mut)
}
// number methods
if let Some(meth) = <T as class::number::PyNumberProtocolImpl>::tp_as_number() {
type_object.tp_as_number = Box::into_raw(Box::new(meth));
} else {
type_object.tp_as_number = ::std::ptr::null_mut()
}
type_object.tp_as_number = to_ptr(<T as class::number::PyNumberProtocolImpl>::tp_as_number());
// mapping methods
if let Some(meth) = <T as class::mapping::PyMappingProtocolImpl>::tp_as_mapping() {
type_object.tp_as_mapping = Box::into_raw(Box::new(meth));
} else {
type_object.tp_as_mapping = ::std::ptr::null_mut()
}
type_object.tp_as_mapping =
to_ptr(<T as class::mapping::PyMappingProtocolImpl>::tp_as_mapping());
// sequence methods
if let Some(meth) = <T as class::sequence::PySequenceProtocolImpl>::tp_as_sequence() {
type_object.tp_as_sequence = Box::into_raw(Box::new(meth));
} else {
type_object.tp_as_sequence = ::std::ptr::null_mut()
}
type_object.tp_as_sequence =
to_ptr(<T as class::sequence::PySequenceProtocolImpl>::tp_as_sequence());
// async methods
async_methods::<T>(type_object);
type_object.tp_as_async = to_ptr(<T as class::pyasync::PyAsyncProtocolImpl>::tp_as_async());
// buffer protocol
if let Some(meth) = <T as class::buffer::PyBufferProtocolImpl>::tp_as_buffer() {
type_object.tp_as_buffer = Box::into_raw(Box::new(meth));
} else {
type_object.tp_as_buffer = ::std::ptr::null_mut()
}
type_object.tp_as_buffer = to_ptr(<T as class::buffer::PyBufferProtocolImpl>::tp_as_buffer());
// normal methods
let (new, init, call, mut methods) = py_class_method_defs::<T>();
@ -428,14 +416,6 @@ where
}
}
fn async_methods<T>(type_info: &mut ffi::PyTypeObject) {
if let Some(meth) = <T as class::pyasync::PyAsyncProtocolImpl>::tp_as_async() {
type_info.tp_as_async = Box::into_raw(Box::new(meth));
} else {
type_info.tp_as_async = ::std::ptr::null_mut()
}
}
unsafe extern "C" fn tp_dealloc_callback<T>(obj: *mut ffi::PyObject)
where
T: PyObjectAlloc,

View file

@ -2,11 +2,11 @@
use crate::ffi;
use crate::object::PyObject;
use crate::types::PyAny;
use crate::AsPyPointer;
use crate::FromPyObject;
use crate::PyResult;
use crate::Python;
use crate::{IntoPyObject, PyTryFrom, ToPyObject};
use crate::{AsPyPointer, FromPy};
use crate::{PyTryFrom, ToPyObject};
/// Represents a Python `bool`.
#[repr(transparent)]
@ -45,10 +45,10 @@ impl ToPyObject for bool {
}
}
impl IntoPyObject for bool {
impl FromPy<bool> for PyObject {
#[inline]
fn into_object(self, py: Python) -> PyObject {
PyBool::new(py, self).into()
fn from_py(other: bool, py: Python) -> Self {
PyBool::new(py, other).into()
}
}

View file

@ -18,8 +18,6 @@ pyobject_native_type!(PyByteArray, ffi::PyByteArray_Type, ffi::PyByteArray_Check
impl PyByteArray {
/// Creates a new Python bytearray object.
/// The byte string is initialized by copying the data from the `&[u8]`.
///
/// Panics if out of memory.
pub fn new<'p>(py: Python<'p>, src: &[u8]) -> &'p PyByteArray {
let ptr = src.as_ptr() as *const c_char;
let len = src.len() as ffi::Py_ssize_t;
@ -47,16 +45,37 @@ impl PyByteArray {
self.len() == 0
}
/// Gets the Python bytearray data as byte slice.
pub fn data(&self) -> &[u8] {
unsafe {
/// Copies the contents of the bytearray to a rust vector
///
/// # Example
///
/// ```
/// # use pyo3::prelude::*;
/// # use pyo3::types::PyByteArray;
/// # use pyo3::types::IntoPyDict;
/// # let gil = GILGuard::acquire();
/// # let py = gil.python();
/// #
/// let bytearray = PyByteArray::new(py, b"Hello World.");
/// let mut copied_message = bytearray.to_vec();
/// assert_eq!(b"Hello World.", copied_message.as_slice());
///
/// copied_message[11] = b'!';
/// assert_eq!(b"Hello World!", copied_message.as_slice());
///
/// let locals = [("bytearray", bytearray)].into_py_dict(py);
/// py.run("assert bytearray == b'Hello World.'", None, Some(locals)).unwrap();
/// ```
pub fn to_vec(&self) -> Vec<u8> {
let slice = unsafe {
let buffer = ffi::PyByteArray_AsString(self.0.as_ptr()) as *mut u8;
let length = ffi::PyByteArray_Size(self.0.as_ptr()) as usize;
slice::from_raw_parts_mut(buffer, length)
}
};
slice.to_vec()
}
/// Resize bytearray object.
/// Resize bytearray object to `len`.
pub fn resize(&self, len: usize) -> PyResult<()> {
unsafe {
let result = ffi::PyByteArray_Resize(self.0.as_ptr(), len as ffi::Py_ssize_t);
@ -84,13 +103,13 @@ mod test {
let src = b"Hello Python";
let bytearray = PyByteArray::new(py, src);
assert_eq!(src.len(), bytearray.len());
assert_eq!(src, bytearray.data());
assert_eq!(src, bytearray.to_vec().as_slice());
let ba: PyObject = bytearray.into();
let bytearray = PyByteArray::from(py, &ba).unwrap();
assert_eq!(src.len(), bytearray.len());
assert_eq!(src, bytearray.data());
assert_eq!(src, bytearray.to_vec().as_slice());
bytearray.resize(20).unwrap();
assert_eq!(20, bytearray.len());

View file

@ -1,8 +1,8 @@
use crate::ffi;
#[cfg(not(PyPy))]
use crate::instance::PyNativeType;
use crate::object::PyObject;
use crate::AsPyPointer;
use crate::PyObject;
use crate::Python;
#[cfg(not(PyPy))]
use std::ops::*;
@ -130,7 +130,7 @@ mod complex_conversion {
use crate::err::PyErr;
use crate::types::PyAny;
use crate::PyResult;
use crate::{FromPyObject, IntoPyObject, ToPyObject};
use crate::{FromPyObject, ToPyObject};
use num_complex::Complex;
impl PyComplex {
@ -150,11 +150,11 @@ mod complex_conversion {
impl ToPyObject for Complex<$float> {
#[inline]
fn to_object(&self, py: Python) -> PyObject {
IntoPyObject::into_object(self.to_owned(), py)
crate::IntoPy::<PyObject>::into_py(self.to_owned(), py)
}
}
impl IntoPyObject for Complex<$float> {
fn into_object(self, py: Python) -> PyObject {
impl crate::IntoPy<PyObject> for Complex<$float> {
fn into_py(self, py: Python) -> PyObject {
unsafe {
let raw_obj =
ffi::PyComplex_FromDoubles(self.re as c_double, self.im as c_double);
@ -196,6 +196,7 @@ mod complex_conversion {
complex_conversion!(f32);
complex_conversion!(f64);
#[allow(clippy::float_cmp)] // The test wants to ensure that no precision was lost on the Python round-trip
#[test]
fn from_complex() {
let gil = Python::acquire_gil();
@ -230,11 +231,13 @@ mod test {
#[test]
fn test_from_double() {
use assert_approx_eq::assert_approx_eq;
let gil = Python::acquire_gil();
let py = gil.python();
let complex = PyComplex::from_doubles(py, 3.0, 1.2);
assert_eq!(complex.real(), 3.0);
assert_eq!(complex.imag(), 1.2);
assert_approx_eq!(complex.real(), 3.0);
assert_approx_eq!(complex.imag(), 1.2);
}
#[cfg(not(Py_LIMITED_API))]
@ -281,8 +284,8 @@ mod test {
let l = PyComplex::from_doubles(py, 3.0, 1.2);
let r = PyComplex::from_doubles(py, 1.0, 2.6);
let res = l / r;
assert_approx_eq!(res.real(), 0.7886597938144329);
assert_approx_eq!(res.imag(), -0.8505154639175257);
assert_approx_eq!(res.real(), 0.788_659_793_814_432_9);
assert_approx_eq!(res.imag(), -0.850_515_463_917_525_7);
}
#[cfg(not(Py_LIMITED_API))]
@ -302,7 +305,7 @@ mod test {
let gil = Python::acquire_gil();
let py = gil.python();
let val = PyComplex::from_doubles(py, 3.0, 1.2);
assert_approx_eq!(val.abs(), 3.2310988842807022);
assert_approx_eq!(val.abs(), 3.231_098_884_280_702_2);
}
#[cfg(not(Py_LIMITED_API))]
@ -313,7 +316,7 @@ mod test {
let l = PyComplex::from_doubles(py, 3.0, 1.2);
let r = PyComplex::from_doubles(py, 1.2, 2.6);
let val = l.pow(r);
assert_approx_eq!(val.real(), -1.4193099970166037);
assert_approx_eq!(val.imag(), -0.5412974660335446);
assert_approx_eq!(val.real(), -1.419_309_997_016_603_7);
assert_approx_eq!(val.imag(), -0.541_297_466_033_544_6);
}
}

View file

@ -1,7 +1,6 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::err::{self, PyErr, PyResult};
use crate::ffi;
use crate::instance::PyNativeType;
use crate::object::PyObject;
use crate::types::{PyAny, PyList};
@ -9,8 +8,9 @@ use crate::AsPyPointer;
#[cfg(not(PyPy))]
use crate::IntoPyPointer;
use crate::Python;
use crate::{IntoPyObject, ToBorrowedObject, ToPyObject};
use std::{cmp, collections, hash, mem};
use crate::{ffi, IntoPy};
use crate::{ToBorrowedObject, ToPyObject};
use std::{cmp, collections, hash};
/// Represents a Python `dict`.
#[repr(transparent)]
@ -172,8 +172,8 @@ impl<'py> Iterator for PyDictIterator<'py> {
#[inline]
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let mut key: *mut ffi::PyObject = mem::uninitialized();
let mut value: *mut ffi::PyObject = mem::uninitialized();
let mut key: *mut ffi::PyObject = std::ptr::null_mut();
let mut value: *mut ffi::PyObject = std::ptr::null_mut();
if ffi::PyDict_Next(self.dict.as_ptr(), &mut self.pos, &mut key, &mut value) != 0 {
let py = self.py;
Some((py.from_borrowed_ptr(key), py.from_borrowed_ptr(value)))
@ -214,29 +214,29 @@ where
}
}
impl<K, V, H> IntoPyObject for collections::HashMap<K, V, H>
impl<K, V, H> IntoPy<PyObject> for collections::HashMap<K, V, H>
where
K: hash::Hash + cmp::Eq + IntoPyObject,
V: IntoPyObject,
K: hash::Hash + cmp::Eq + IntoPy<PyObject>,
V: IntoPy<PyObject>,
H: hash::BuildHasher,
{
fn into_object(self, py: Python) -> PyObject {
fn into_py(self, py: Python) -> PyObject {
let iter = self
.into_iter()
.map(|(k, v)| (k.into_object(py), v.into_object(py)));
.map(|(k, v)| (k.into_py(py), v.into_py(py)));
IntoPyDict::into_py_dict(iter, py).into()
}
}
impl<K, V> IntoPyObject for collections::BTreeMap<K, V>
impl<K, V> IntoPy<PyObject> for collections::BTreeMap<K, V>
where
K: cmp::Eq + IntoPyObject,
V: IntoPyObject,
K: cmp::Eq + IntoPy<PyObject>,
V: IntoPy<PyObject>,
{
fn into_object(self, py: Python) -> PyObject {
fn into_py(self, py: Python) -> PyObject {
let iter = self
.into_iter()
.map(|(k, v)| (k.into_object(py), v.into_object(py)));
.map(|(k, v)| (k.into_py(py), v.into_py(py)));
IntoPyDict::into_py_dict(iter, py).into()
}
}
@ -304,12 +304,13 @@ where
#[cfg(test)]
mod test {
use crate::conversion::IntoPy;
use crate::instance::AsPyRef;
use crate::types::dict::IntoPyDict;
use crate::types::{PyDict, PyList, PyTuple};
use crate::ObjectProtocol;
use crate::Python;
use crate::{IntoPyObject, PyTryFrom, ToPyObject};
use crate::{ObjectProtocol, PyObject};
use crate::{PyTryFrom, ToPyObject};
use std::collections::{BTreeMap, HashMap};
#[test]
@ -600,7 +601,7 @@ mod test {
let mut map = HashMap::<i32, i32>::new();
map.insert(1, 1);
let m = map.into_object(py);
let m: PyObject = map.into_py(py);
let py_map = <PyDict as PyTryFrom>::try_from(m.as_ref(py)).unwrap();
assert!(py_map.len() == 1);
@ -622,14 +623,14 @@ mod test {
}
#[test]
fn test_btreemap_into_object() {
fn test_btreemap_into_py() {
let gil = Python::acquire_gil();
let py = gil.python();
let mut map = BTreeMap::<i32, i32>::new();
map.insert(1, 1);
let m = map.into_object(py);
let m: PyObject = map.into_py(py);
let py_map = <PyDict as PyTryFrom>::try_from(m.as_ref(py)).unwrap();
assert!(py_map.len() == 1);

View file

@ -8,11 +8,11 @@ use crate::instance::PyNativeType;
use crate::object::PyObject;
use crate::objectprotocol::ObjectProtocol;
use crate::types::PyAny;
use crate::AsPyPointer;
use crate::FromPyObject;
use crate::PyResult;
use crate::Python;
use crate::{IntoPyObject, ToPyObject};
use crate::ToPyObject;
use crate::{AsPyPointer, FromPy};
use std::os::raw::c_double;
/// Represents a Python `float` object.
@ -44,9 +44,9 @@ impl ToPyObject for f64 {
}
}
impl IntoPyObject for f64 {
fn into_object(self, py: Python) -> PyObject {
PyFloat::new(py, self).into()
impl FromPy<f64> for PyObject {
fn from_py(other: f64, py: Python) -> Self {
PyFloat::new(py, other).into()
}
}
@ -70,9 +70,9 @@ impl ToPyObject for f32 {
}
}
impl IntoPyObject for f32 {
fn into_object(self, py: Python) -> PyObject {
PyFloat::new(py, f64::from(self)).into()
impl FromPy<f32> for PyObject {
fn from_py(other: f32, py: Python) -> Self {
PyFloat::new(py, f64::from(other)).into()
}
}
@ -91,11 +91,13 @@ mod test {
($func_name:ident, $t1:ty, $t2:ty) => (
#[test]
fn $func_name() {
use assert_approx_eq::assert_approx_eq;
let gil = Python::acquire_gil();
let py = gil.python();
let val = 123 as $t1;
let obj = val.to_object(py);
assert_eq!(obj.extract::<$t2>(py).unwrap(), val as $t2);
assert_approx_eq!(obj.extract::<$t2>(py).unwrap(), val as $t2);
}
)
);
@ -106,10 +108,12 @@ mod test {
#[test]
fn test_as_double_macro() {
use assert_approx_eq::assert_approx_eq;
let gil = Python::acquire_gil();
let py = gil.python();
let v = 1.23f64;
let obj = v.to_object(py);
assert_eq!(v, unsafe { PyFloat_AS_DOUBLE(obj.as_ptr()) });
assert_approx_eq!(v, unsafe { PyFloat_AS_DOUBLE(obj.as_ptr()) });
}
}

View file

@ -40,6 +40,10 @@ impl<'p> PyIterator<'p> {
{
unsafe {
let ptr = ffi::PyObject_GetIter(obj.as_ptr());
// Returns NULL if an object cannot be iterated.
if ptr.is_null() {
return Err(PyDowncastError);
}
if ffi::PyIter_Check(ptr) != 0 {
// this is not right, but this cause of segfault check #71

View file

@ -7,10 +7,10 @@ use crate::ffi::{self, Py_ssize_t};
use crate::instance::PyNativeType;
use crate::object::PyObject;
use crate::types::PyAny;
use crate::AsPyPointer;
use crate::IntoPyPointer;
use crate::Python;
use crate::{IntoPyObject, ToBorrowedObject, ToPyObject};
use crate::{AsPyPointer, IntoPy};
use crate::{ToBorrowedObject, ToPyObject};
/// Represents a Python `list`.
#[repr(transparent)]
@ -186,15 +186,15 @@ where
}
}
impl<T> IntoPyObject for Vec<T>
impl<T> IntoPy<PyObject> for Vec<T>
where
T: IntoPyObject,
T: IntoPy<PyObject>,
{
fn into_object(self, py: Python) -> PyObject {
fn into_py(self, py: Python) -> PyObject {
unsafe {
let ptr = ffi::PyList_New(self.len() as Py_ssize_t);
for (i, e) in self.into_iter().enumerate() {
let obj = e.into_object(py).into_ptr();
let obj = e.into_py(py).into_ptr();
ffi::PyList_SetItem(ptr, i as Py_ssize_t, obj);
}
PyObject::from_owned_ptr_or_panic(py, ptr)

View file

@ -9,8 +9,9 @@ use crate::instance::PyNativeType;
use crate::object::PyObject;
use crate::types::PyAny;
use crate::AsPyPointer;
use crate::IntoPy;
use crate::Python;
use crate::{FromPyObject, IntoPyObject, ToPyObject};
use crate::{FromPyObject, ToPyObject};
use num_traits::cast::cast;
use std::i64;
use std::os::raw::c_int;
@ -34,12 +35,12 @@ macro_rules! int_fits_larger_int (
impl ToPyObject for $rust_type {
#[inline]
fn to_object(&self, py: Python) -> PyObject {
(*self as $larger_type).into_object(py)
(*self as $larger_type).into_py(py)
}
}
impl IntoPyObject for $rust_type {
fn into_object(self, py: Python) -> PyObject {
(self as $larger_type).into_object(py)
impl IntoPy<PyObject> for $rust_type {
fn into_py(self, py: Python) -> PyObject {
(self as $larger_type).into_py(py)
}
}
@ -62,11 +63,11 @@ macro_rules! int_convert_bignum (
impl ToPyObject for $rust_type {
#[inline]
fn to_object(&self, py: Python) -> PyObject {
self.into_object(py)
(*self).into_py(py)
}
}
impl IntoPyObject for $rust_type {
fn into_object(self, py: Python) -> PyObject {
impl IntoPy<PyObject> for $rust_type {
fn into_py(self, py: Python) -> PyObject {
unsafe {
// TODO: Replace this with functions from the from_bytes family
// Once they are stabilized
@ -140,9 +141,9 @@ macro_rules! int_fits_c_long (
}
}
}
impl IntoPyObject for $rust_type {
impl IntoPy<PyObject> for $rust_type {
#![cfg_attr(feature="cargo-clippy", allow(clippy::cast_lossless))]
fn into_object(self, py: Python) -> PyObject {
fn into_py(self, py: Python) -> PyObject {
unsafe {
PyObject::from_owned_ptr_or_panic(py, ffi::PyLong_FromLong(self as c_long))
}
@ -181,9 +182,9 @@ macro_rules! int_convert_u64_or_i64 (
}
}
}
impl IntoPyObject for $rust_type {
impl IntoPy<PyObject> for $rust_type {
#[inline]
fn into_object(self, py: Python) -> PyObject {
fn into_py(self, py: Python) -> PyObject {
unsafe {
PyObject::from_owned_ptr_or_panic(py, $pylong_from_ll_or_ull(self))
}

View file

@ -8,6 +8,7 @@ use crate::object::PyObject;
use crate::AsPyPointer;
use crate::Python;
use crate::{ToBorrowedObject, ToPyObject};
use std::ptr;
use std::{collections, hash};
/// Represents a Python `set`
@ -28,6 +29,11 @@ impl PySet {
unsafe { py.from_owned_ptr_or_err(ffi::PySet_New(list.as_ptr())) }
}
/// Creates a new empty set
pub fn empty<'p>(py: Python<'p>) -> PyResult<&'p PySet> {
unsafe { py.from_owned_ptr_or_err(ffi::PySet_New(ptr::null_mut())) }
}
/// Remove all elements from the set.
#[inline]
pub fn clear(&self) {
@ -128,6 +134,11 @@ impl PyFrozenSet {
unsafe { py.from_owned_ptr_or_err(ffi::PyFrozenSet_New(list.as_ptr())) }
}
/// Creates a new empty frozen set
pub fn empty<'p>(py: Python<'p>) -> PyResult<&'p PySet> {
unsafe { py.from_owned_ptr_or_err(ffi::PyFrozenSet_New(ptr::null_mut())) }
}
/// Return the number of items in the set.
/// This is equivalent to len(p) on a set.
#[inline]
@ -177,6 +188,14 @@ mod test {
assert!(PySet::new(py, &[v]).is_err());
}
#[test]
fn test_set_empty() {
let gil = Python::acquire_gil();
let py = gil.python();
let set = PySet::empty(py).unwrap();
assert_eq!(0, set.len());
}
#[test]
fn test_set_len() {
let gil = Python::acquire_gil();
@ -264,6 +283,14 @@ mod test {
assert!(PyFrozenSet::new(py, &[v]).is_err());
}
#[test]
fn test_frozenset_empty() {
let gil = Python::acquire_gil();
let py = gil.python();
let set = PyFrozenSet::empty(py).unwrap();
assert_eq!(0, set.len());
}
#[test]
fn test_frozenset_contains() {
let gil = Python::acquire_gil();

View file

@ -1,26 +1,32 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::conversion::FromPyObject;
use crate::conversion::{IntoPyObject, PyTryFrom, ToPyObject};
use crate::conversion::{PyTryFrom, ToPyObject};
use crate::err::{PyErr, PyResult};
use crate::exceptions;
use crate::ffi;
use crate::instance::PyNativeType;
use crate::object::PyObject;
use crate::types::PyAny;
use crate::AsPyPointer;
use crate::Python;
use crate::{exceptions, IntoPy};
use crate::{ffi, FromPy};
use std::borrow::Cow;
use std::ops::Index;
use std::os::raw::c_char;
use std::{mem, str};
use std::slice::SliceIndex;
use std::str;
/// Represents a Python `string`.
///
/// This type is immutable
#[repr(transparent)]
pub struct PyString(PyObject);
pyobject_native_type!(PyString, ffi::PyUnicode_Type, ffi::PyUnicode_Check);
/// Represents a Python `byte` string.
/// Represents a Python `bytes`.
///
/// This type is immutable
#[repr(transparent)]
pub struct PyBytes(PyObject);
@ -56,7 +62,7 @@ impl PyString {
#[inline]
pub fn as_bytes(&self) -> &[u8] {
unsafe {
let mut size: ffi::Py_ssize_t = mem::uninitialized();
let mut size: ffi::Py_ssize_t = 0;
let data = ffi::PyUnicode_AsUTF8AndSize(self.0.as_ptr(), &mut size) as *const u8;
// PyUnicode_AsUTF8AndSize would return null if the pointer did not reference a valid
// unicode object, but because we have a valid PyString, assume success
@ -120,6 +126,15 @@ impl PyBytes {
}
}
/// This is the same way [Vec] is indexed
impl<I: SliceIndex<[u8]>> Index<I> for PyBytes {
type Output = I::Output;
fn index(&self, index: I) -> &Self::Output {
&self.as_bytes()[index]
}
}
/// Converts Rust `str` to Python object.
/// See `PyString::new` for details on the conversion.
impl ToPyObject for str {
@ -129,9 +144,9 @@ impl ToPyObject for str {
}
}
impl<'a> IntoPyObject for &'a str {
impl<'a> IntoPy<PyObject> for &'a str {
#[inline]
fn into_object(self, py: Python) -> PyObject {
fn into_py(self, py: Python) -> PyObject {
PyString::new(py, self).into()
}
}
@ -154,16 +169,15 @@ impl ToPyObject for String {
}
}
impl IntoPyObject for String {
#[inline]
fn into_object(self, py: Python) -> PyObject {
PyString::new(py, &self).into()
impl FromPy<String> for PyObject {
fn from_py(other: String, py: Python) -> Self {
PyString::new(py, &other).into()
}
}
impl<'a> IntoPyObject for &'a String {
impl<'a> IntoPy<PyObject> for &'a String {
#[inline]
fn into_object(self, py: Python) -> PyObject {
fn into_py(self, py: Python) -> PyObject {
PyString::new(py, self).into()
}
}
@ -203,7 +217,7 @@ impl<'source> FromPyObject<'source> for String {
#[cfg(test)]
mod test {
use super::PyString;
use super::{PyBytes, PyString};
use crate::instance::AsPyRef;
use crate::object::PyObject;
use crate::Python;
@ -261,4 +275,12 @@ mod test {
assert!(py_string.to_string().is_ok());
assert_eq!(Cow::Borrowed(s), py_string.to_string().unwrap());
}
#[test]
fn test_bytes_index() {
let gil = Python::acquire_gil();
let py = gil.python();
let bytes = PyBytes::new(py, b"Hello World");
assert_eq!(bytes[1], b'e');
}
}

View file

@ -10,7 +10,7 @@ use crate::types::PyAny;
use crate::AsPyPointer;
use crate::IntoPyPointer;
use crate::Python;
use crate::{FromPyObject, IntoPy, IntoPyObject, PyTryFrom, ToPyObject};
use crate::{FromPyObject, IntoPy, PyTryFrom, ToPyObject};
use std::slice;
/// Represents a Python `tuple` object.
@ -21,10 +21,7 @@ pyobject_native_type!(PyTuple, ffi::PyTuple_Type, ffi::PyTuple_Check);
impl PyTuple {
/// Construct a new tuple with the given elements.
pub fn new<'p, T, U>(
py: Python<'p>,
elements: impl IntoIterator<Item = T, IntoIter = U>,
) -> &'p PyTuple
pub fn new<T, U>(py: Python, elements: impl IntoIterator<Item = T, IntoIter = U>) -> &PyTuple
where
T: ToPyObject,
U: ExactSizeIterator<Item = T>,
@ -41,7 +38,7 @@ impl PyTuple {
}
/// Retrieves the empty tuple.
pub fn empty<'p>(py: Python<'p>) -> &'p PyTuple {
pub fn empty(py: Python) -> &PyTuple {
unsafe { py.from_owned_ptr(ffi::PyTuple_New(0)) }
}
@ -157,26 +154,26 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+
fn to_object(&self, py: Python) -> PyObject {
unsafe {
let ptr = ffi::PyTuple_New($length);
$(ffi::PyTuple_SetItem(ptr, $n, self.$n.to_object(py).into_ptr());)+;
$(ffi::PyTuple_SetItem(ptr, $n, self.$n.to_object(py).into_ptr());)+
PyObject::from_owned_ptr_or_panic(py, ptr)
}
}
}
impl <$($T: IntoPyObject),+> IntoPyObject for ($($T,)+) {
fn into_object(self, py: Python) -> PyObject {
impl <$($T: IntoPy<PyObject>),+> IntoPy<PyObject> for ($($T,)+) {
fn into_py(self, py: Python) -> PyObject {
unsafe {
let ptr = ffi::PyTuple_New($length);
$(ffi::PyTuple_SetItem(ptr, $n, self.$n.into_object(py).into_ptr());)+;
$(ffi::PyTuple_SetItem(ptr, $n, self.$n.into_py(py).into_ptr());)+
PyObject::from_owned_ptr_or_panic(py, ptr)
}
}
}
impl <$($T: IntoPyObject),+> IntoPy<Py<PyTuple>> for ($($T,)+) {
impl <$($T: IntoPy<PyObject>),+> IntoPy<Py<PyTuple>> for ($($T,)+) {
fn into_py(self, py: Python) -> Py<PyTuple> {
unsafe {
let ptr = ffi::PyTuple_New($length);
$(ffi::PyTuple_SetItem(ptr, $n, self.$n.into_object(py).into_ptr());)+;
$(ffi::PyTuple_SetItem(ptr, $n, self.$n.into_py(py).into_ptr());)+
Py::from_owned_ptr_or_panic(ptr)
}
}

View file

@ -1,3 +1,5 @@
#![feature(specialization)]
use pyo3::class::basic::CompareOp;
use pyo3::class::*;
use pyo3::prelude::*;
@ -186,6 +188,28 @@ fn binary_arithmetic() {
py_run!(py, c, "assert 1 | c == '1 | BA'");
}
#[pyclass]
struct RhsArithmetic {}
#[pyproto]
impl PyNumberProtocol for RhsArithmetic {
fn __radd__(&self, other: &PyAny) -> PyResult<String> {
Ok(format!("{:?} + RA", other))
}
}
#[test]
fn rhs_arithmetic() {
let gil = Python::acquire_gil();
let py = gil.python();
let c = Py::new(py, RhsArithmetic {}).unwrap();
py_run!(py, c, "assert c.__radd__(1) == '1 + RA'");
// TODO: commented out for now until reflected arithemtics gets fixed.
// see discussion here: https://github.com/PyO3/pyo3/pull/550
// py_run!(py, c, "assert 1 + c == '1 + RA'");
}
#[pyclass]
struct RichComparisons {}

2
tests/test_compile_error.rs Normal file → Executable file
View file

@ -1,6 +1,6 @@
#[test]
#[cfg(testkcovstopmarker)]
fn test_compile_errors() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/reject_generics.rs");
t.compile_fail("tests/ui/too_many_args_to_getter.rs");
}

View file

@ -4,6 +4,7 @@ use pyo3::ffi::*;
use pyo3::prelude::*;
use pyo3::types::{IntoPyDict, PyAny};
#[allow(clippy::trivially_copy_pass_by_ref)]
fn _get_subclasses<'p>(
py: &'p Python,
py_type: &str,
@ -101,6 +102,7 @@ fn test_delta_check() {
#[test]
fn test_datetime_utc() {
use assert_approx_eq::assert_approx_eq;
use pyo3::types::PyDateTime;
let gil = Python::acquire_gil();
@ -119,10 +121,11 @@ fn test_datetime_utc() {
.unwrap()
.extract()
.unwrap();
assert_eq!(offset, 0f32);
assert_approx_eq!(offset, 0f32);
}
static INVALID_DATES: &'static [(i32, u8, u8)] = &[
#[cfg(Py_3_6)]
static INVALID_DATES: &[(i32, u8, u8)] = &[
(-1, 1, 1),
(0, 1, 1),
(10000, 1, 1),
@ -134,7 +137,8 @@ static INVALID_DATES: &'static [(i32, u8, u8)] = &[
(2018, 1, 32),
];
static INVALID_TIMES: &'static [(u8, u8, u8, u32)] =
#[cfg(Py_3_6)]
static INVALID_TIMES: &[(u8, u8, u8, u32)] =
&[(25, 0, 0, 0), (255, 0, 0, 0), (0, 60, 0, 0), (0, 0, 61, 0)];
#[cfg(Py_3_6)]
@ -145,7 +149,7 @@ fn test_pydate_out_of_bounds() {
// This test is an XFAIL on Python < 3.6 until bounds checking is implemented
let gil = Python::acquire_gil();
let py = gil.python();
for val in INVALID_DATES.into_iter() {
for val in INVALID_DATES {
let (year, month, day) = val;
let dt = PyDate::new(py, *year, *month, *day);
dt.unwrap_err();

View file

@ -12,5 +12,5 @@ fn iter_dict_nosegv() {
let i: u64 = k.extract().unwrap();
sum += i;
}
assert_eq!(sum, 49999995000000);
assert_eq!(sum, 49_999_995_000_000);
}

26
tests/test_dunder.rs Normal file → Executable file
View file

@ -391,11 +391,11 @@ impl<'p> PyMappingProtocol<'p> for Test {
if let Ok(slice) = idx.cast_as::<PySlice>() {
let indices = slice.indices(1000)?;
if indices.start == 100 && indices.stop == 200 && indices.step == 1 {
return Ok("slice".into_object(gil.python()));
return Ok("slice".into_py(gil.python()));
}
} else if let Ok(idx) = idx.extract::<isize>() {
if idx == 1 {
return Ok("int".into_object(gil.python()));
return Ok("int".into_py(gil.python()));
}
}
Err(PyErr::new::<ValueError, _>("error"))
@ -462,3 +462,25 @@ fn weakref_dunder_dict_support() {
"import weakref; assert weakref.ref(inst)() is inst; inst.a = 1; assert inst.a == 1"
);
}
#[pyclass]
struct ClassWithGetAttr {
#[pyo3(get, set)]
data: u32,
}
#[pyproto]
impl PyObjectProtocol for ClassWithGetAttr {
fn __getattr__(&self, _name: &str) -> PyResult<u32> {
Ok(self.data * 2)
}
}
#[test]
fn getattr_doesnt_override_member() {
let gil = Python::acquire_gil();
let py = gil.python();
let inst = PyRef::new(py, ClassWithGetAttr { data: 4 }).unwrap();
py_assert!(py, inst, "inst.data == 4");
py_assert!(py, inst, "inst.a == 8");
}

77
tests/test_exceptions.rs Normal file
View file

@ -0,0 +1,77 @@
use pyo3::prelude::*;
use pyo3::{exceptions, py_run, wrap_pyfunction, PyErr, PyResult};
use std::error::Error;
use std::fmt;
use std::fs::File;
mod common;
#[pyfunction]
fn fail_to_open_file() -> PyResult<()> {
File::open("not_there.txt")?;
Ok(())
}
#[test]
#[cfg(not(target_os = "windows"))]
fn test_filenotfounderror() {
let gil = Python::acquire_gil();
let py = gil.python();
let fail_to_open_file = wrap_pyfunction!(fail_to_open_file)(py);
py_run!(
py,
fail_to_open_file,
r#"
try:
fail_to_open_file()
except FileNotFoundError as e:
assert str(e) == "No such file or directory (os error 2)"
"#
);
}
#[derive(Debug)]
struct CustomError;
impl Error for CustomError {}
impl fmt::Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Oh no!")
}
}
impl std::convert::From<CustomError> for PyErr {
fn from(err: CustomError) -> PyErr {
exceptions::OSError::py_err(err.to_string())
}
}
fn fail_with_custom_error() -> Result<(), CustomError> {
Err(CustomError)
}
#[pyfunction]
fn call_fail_with_custom_error() -> PyResult<()> {
fail_with_custom_error()?;
Ok(())
}
#[test]
fn test_custom_error() {
let gil = Python::acquire_gil();
let py = gil.python();
let call_fail_with_custom_error = wrap_pyfunction!(call_fail_with_custom_error)(py);
py_run!(
py,
call_fail_with_custom_error,
r#"
try:
call_fail_with_custom_error()
except OSError as e:
assert str(e) == "Oh no!"
"#
);
}

View file

@ -94,7 +94,7 @@ impl Drop for ClassWithDrop {
let py = Python::assume_gil_acquired();
let _empty1: Py<PyTuple> = FromPy::from_py(PyTuple::empty(py), py);
let _empty2 = PyTuple::empty(py).into_object(py);
let _empty2: PyObject = PyTuple::empty(py).into_py(py);
let _empty3: &PyAny = py.from_owned_ptr(ffi::PyTuple_New(0));
}
}
@ -110,9 +110,9 @@ fn create_pointers_in_drop() {
{
let gil = Python::acquire_gil();
let py = gil.python();
let empty = PyTuple::empty(py).into_object(py);
let empty: PyObject = PyTuple::empty(py).into_py(py);
ptr = empty.as_ptr();
// substract 2, because `PyTuple::empty(py).into_object(py)` increases the refcnt by 2
// substract 2, because `PyTuple::empty(py).into_py(py)` increases the refcnt by 2
cnt = empty.get_refcnt() - 2;
let inst = Py::new(py, ClassWithDrop {}).unwrap();
drop(inst);
@ -180,15 +180,20 @@ fn gc_integration() {
#[pyclass(gc)]
struct GCIntegration2 {}
#[pyproto]
impl PyGCProtocol for GCIntegration2 {
fn __traverse__(&self, _visit: PyVisit) -> Result<(), PyTraverseError> {
Ok(())
}
fn __clear__(&mut self) {}
}
#[test]
fn gc_integration2() {
let gil = Python::acquire_gil();
let py = gil.python();
// Temporarily disable pythons garbage collector to avoid a race condition
py.run("import gc; gc.disable()", None, None).unwrap();
let inst = PyRef::new(py, GCIntegration2 {}).unwrap();
py_run!(py, inst, "assert inst in gc.get_objects()");
py.run("gc.enable()", None, None).unwrap();
py_run!(py, inst, "import gc; assert inst in gc.get_objects()");
}
#[pyclass(weakref)]

View file

@ -1,6 +1,6 @@
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::IntoPyDict;
use pyo3::types::{IntoPyDict, PyList};
use std::isize;
mod common;
@ -32,10 +32,16 @@ impl ClassWithProperties {
fn get_unwrapped(&self) -> i32 {
self.num
}
#[setter]
fn set_unwrapped(&mut self, value: i32) {
self.num = value;
}
#[getter]
fn get_data_list<'py>(&self, py: Python<'py>) -> &'py PyList {
PyList::new(py, &[self.num])
}
}
#[test]
@ -48,12 +54,12 @@ fn class_with_properties() {
py_run!(py, inst, "assert inst.get_num() == 10");
py_run!(py, inst, "assert inst.get_num() == inst.DATA");
py_run!(py, inst, "inst.DATA = 20");
py_run!(py, inst, "assert inst.get_num() == 20");
py_run!(py, inst, "assert inst.get_num() == inst.DATA");
py_run!(py, inst, "assert inst.get_num() == 20 == inst.DATA");
py_run!(py, inst, "assert inst.get_num() == inst.unwrapped == 20");
py_run!(py, inst, "inst.unwrapped = 42");
py_run!(py, inst, "assert inst.get_num() == inst.unwrapped == 42");
py_run!(py, inst, "assert inst.data_list == [42]");
let d = [("C", py.get_type::<ClassWithProperties>())].into_py_dict(py);
py.run(

View file

@ -1,5 +1,6 @@
use pyo3::prelude::*;
use pyo3::py_run;
#[cfg(feature = "unsound-subclass")]
use pyo3::types::IntoPyDict;
use std::isize;
@ -11,9 +12,11 @@ struct BaseClass {
val1: usize,
}
#[cfg(feature = "unsound-subclass")]
#[pyclass(subclass)]
struct SubclassAble {}
#[cfg(feature = "unsound-subclass")]
#[test]
fn subclass() {
let gil = Python::acquire_gil();

View file

@ -17,6 +17,9 @@ struct Reader {
#[pymethods]
impl Reader {
fn clone_ref(slf: PyRef<Self>) -> PyRef<Self> {
slf
}
fn get_iter(slf: PyRef<Self>, keys: Py<PyBytes>) -> PyResult<Iter> {
Ok(Iter {
reader: slf.into(),
@ -70,15 +73,18 @@ impl PyIterProtocol for Iter {
}
}
fn reader() -> Reader {
let reader = [(1, "a"), (2, "b"), (3, "c"), (4, "d"), (5, "e")];
Reader {
inner: reader.iter().map(|(k, v)| (*k, v.to_string())).collect(),
}
}
#[test]
fn test_nested_iter() {
let gil = Python::acquire_gil();
let py = gil.python();
let reader = [(1, "a"), (2, "b"), (3, "c"), (4, "d"), (5, "e")];
let reader = Reader {
inner: reader.iter().map(|(k, v)| (*k, v.to_string())).collect(),
}
.into_object(py);
let reader: PyObject = reader().into_py(py);
py_assert!(
py,
reader,
@ -86,23 +92,23 @@ fn test_nested_iter() {
);
}
#[test]
fn test_clone_ref() {
let gil = Python::acquire_gil();
let py = gil.python();
let reader: PyObject = reader().into_py(py);
py_assert!(py, reader, "reader == reader.clone_ref()");
}
#[test]
fn test_nested_iter_reset() {
let gil = Python::acquire_gil();
let py = gil.python();
let reader = [(1, "a"), (2, "b"), (3, "c"), (4, "d"), (5, "e")];
let reader = PyRef::new(
py,
Reader {
inner: reader.iter().map(|(k, v)| (*k, v.to_string())).collect(),
},
)
.unwrap();
let obj = reader.into_object(py);
let reader = PyRef::new(py, reader()).unwrap();
py_assert!(
py,
obj,
"list(obj.get_iter_and_reset(bytes([3, 5, 2]))) == ['c', 'e', 'b']"
reader,
"list(reader.get_iter_and_reset(bytes([3, 5, 2]))) == ['c', 'e', 'b']"
);
assert!(reader.inner.is_empty());
}

View file

@ -40,8 +40,8 @@ impl PySequenceProtocol for ByteSequence {
fn __getitem__(&self, idx: isize) -> PyResult<u8> {
self.elements
.get(idx as usize)
.map(|&byte| byte)
.ok_or(IndexError::py_err("list index out of range"))
.copied()
.ok_or_else(|| IndexError::py_err("list index out of range"))
}
fn __setitem__(&mut self, idx: isize, value: u8) -> PyResult<()> {

View file

@ -111,7 +111,7 @@ fn pytuple_pyclass_iter() {
PyRef::new(py, SimplePyClass {}).unwrap(),
PyRef::new(py, SimplePyClass {}).unwrap(),
]
.into_iter(),
.iter(),
);
py_assert!(py, tup, "type(tup[0]).__name__ == 'SimplePyClass'");
py_assert!(py, tup, "type(tup[0]).__name__ == type(tup[0]).__name__");
@ -170,3 +170,13 @@ fn test_pickle() {
"#
);
}
#[test]
fn incorrect_iter() {
let gil = Python::acquire_gil();
let py = gil.python();
let int = 13isize.to_object(py);
let int_ref = int.as_ref(py);
// Should not segfault.
assert!(int_ref.iter().is_err());
}

View file

@ -3,3 +3,5 @@ error: #[pyclass] cannot have generic parameters
|
4 | struct ClassWithGenerics<A> {
| ^^^
error: Could not compile `pyo3-tests`.

View file

@ -0,0 +1,14 @@
use pyo3::prelude::*;
#[pyclass]
struct ClassWithGetter {
a: u32,
}
#[pymethods]
impl ClassWithGetter {
#[getter]
fn get_num(&self, index: u32) {}
}
fn main() {}

View file

@ -0,0 +1,7 @@
error: Getter function can only have one argument of type pyo3::Python!
--> $DIR/too_many_args_to_getter.rs:11:30
|
11 | fn get_num(&self, index: u32) {}
| ^^^
error: Could not compile `pyo3-tests`.