move ffi module to separate crate

This commit is contained in:
DSPOM 2022-01-25 14:46:27 +01:00
parent 6b95118c23
commit 6a9a9ba38a
No known key found for this signature in database
GPG Key ID: 00818B9EE49CF750
91 changed files with 1307 additions and 783 deletions

View File

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

View File

@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `pyo3-build-config` no longer enables the `resolve-config` feature by default. [#2008](https://github.com/PyO3/pyo3/pull/2008) - `pyo3-build-config` no longer enables the `resolve-config` feature by default. [#2008](https://github.com/PyO3/pyo3/pull/2008)
- Update `inventory` optional dependency to 0.2. [#2019](https://github.com/PyO3/pyo3/pull/2019) - Update `inventory` optional dependency to 0.2. [#2019](https://github.com/PyO3/pyo3/pull/2019)
- Drop `paste` dependency. [#2081](https://github.com/PyO3/pyo3/pull/2081) - Drop `paste` dependency. [#2081](https://github.com/PyO3/pyo3/pull/2081)
- The bindings found `pyo3::ffi` are now a re-export of the new `pyo3-ffi` crate. [#2126](https://github.com/PyO3/pyo3/pull/2126)
### Added ### Added
@ -50,6 +51,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076) [#2081](https://github.com/PyO3/pyo3/pull/2081) - `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076) [#2081](https://github.com/PyO3/pyo3/pull/2081)
- `__ipow__` now supports modulo argument on Python 3.8+. [#2083](https://github.com/PyO3/pyo3/pull/2083) - `__ipow__` now supports modulo argument on Python 3.8+. [#2083](https://github.com/PyO3/pyo3/pull/2083)
- `pyo3-macros-backend` is now compiled with PyO3 cfgs to enable different magic method definitions based on version. [#2083](https://github.com/PyO3/pyo3/pull/2083) - `pyo3-macros-backend` is now compiled with PyO3 cfgs to enable different magic method definitions based on version. [#2083](https://github.com/PyO3/pyo3/pull/2083)
- `_PyCFunctionFast` now correctly reflects the signature defined in the [Python docs](https://docs.python.org/3/c-api/structures.html#c._PyCFunctionFast). [#2126](https://github.com/PyO3/pyo3/pull/2126)
- `PyDateTimeAPI` and `PyDateTime_TimeZone_UTC` are are now unsafe functions instead of statics. [#2126](https://github.com/PyO3/pyo3/pull/2126)
- `PyDateTimeAPI` does not implicitly call `PyDateTime_IMPORT` anymore to reflect the original Python API more closely. Before the first call to `PyDateTime_IMPORT` a null pointer is returned. Therefore before calling any of the following FFI functions `PyDateTime_IMPORT` must be called to avoid undefined behaviour: [#2126](https://github.com/PyO3/pyo3/pull/2126)
- `PyDateTime_TimeZone_UTC`
- `PyDate_Check`
- `PyDate_CheckExact`
- `PyDateTime_Check`
- `PyDateTime_CheckExact`
- `PyTime_Check`
- `PyTime_CheckExact`
- `PyDelta_Check`
- `PyDelta_CheckExact`
- `PyTZInfo_Check`
- `PyTZInfo_CheckExact`
- `PyDateTime_FromTimestamp`
- `PyDate_FromTimestamp`
### Removed ### Removed

View File

@ -19,6 +19,9 @@ cfg-if = "1.0"
libc = "0.2.62" libc = "0.2.62"
parking_lot = "0.11.0" parking_lot = "0.11.0"
# ffi bindings to the python interpreter, split into a seperate crate so they can be used independently
pyo3-ffi = { path = "pyo3-ffi", version = "=0.15.1" }
# support crates for macros feature # support crates for macros feature
pyo3-macros = { path = "pyo3-macros", version = "=0.15.1", optional = true } pyo3-macros = { path = "pyo3-macros", version = "=0.15.1", optional = true }
indoc = { version = "1.0.3", optional = true } indoc = { version = "1.0.3", optional = true }
@ -60,16 +63,16 @@ multiple-pymethods = ["inventory", "pyo3-macros/multiple-pymethods"]
# Use this feature when building an extension module. # Use this feature when building an extension module.
# It tells the linker to keep the python symbols unresolved, # It tells the linker to keep the python symbols unresolved,
# so that the module can also be used with statically linked python interpreters. # so that the module can also be used with statically linked python interpreters.
extension-module = [] extension-module = ["pyo3-ffi/extension-module"]
# Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more. # Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more.
abi3 = ["pyo3-build-config/abi3"] abi3 = ["pyo3-build-config/abi3", "pyo3-ffi/abi3"]
# With abi3, we can manually set the minimum Python version. # With abi3, we can manually set the minimum Python version.
abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37"] abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37", "pyo3-ffi/abi3-py37"]
abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38"] abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38", "pyo3-ffi/abi3-py38"]
abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39"] abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39", "pyo3-ffi/abi3-py39"]
abi3-py310 = ["abi3", "pyo3-build-config/abi3-py310"] abi3-py310 = ["abi3", "pyo3-build-config/abi3-py310", "pyo3-ffi/abi3-py310"]
# Changes `Python::with_gil` and `Python::acquire_gil` to automatically initialize the # Changes `Python::with_gil` and `Python::acquire_gil` to automatically initialize the
# Python interpreter if needed. # Python interpreter if needed.
@ -126,6 +129,7 @@ harness = false
[workspace] [workspace]
members = [ members = [
"pyo3-ffi",
"pyo3-macros", "pyo3-macros",
"pyo3-macros-backend", "pyo3-macros-backend",
"pytests/pyo3-benchmarks", "pytests/pyo3-benchmarks",

103
build.rs
View File

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

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

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

201
pyo3-ffi/README.md Normal file
View File

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

119
pyo3-ffi/build.rs Normal file
View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
use crate::ffi::longobject::PyLongObject; use crate::longobject::PyLongObject;
use crate::ffi::object::*; use crate::object::*;
use std::os::raw::{c_int, c_long}; use std::os::raw::{c_int, c_long};
#[cfg_attr(windows, link(name = "pythonXY"))] #[cfg_attr(windows, link(name = "pythonXY"))]

View File

@ -1,5 +1,5 @@
use crate::ffi::object::*; use crate::object::*;
use crate::ffi::pyport::Py_ssize_t; use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
#[cfg_attr(windows, link(name = "pythonXY"))] #[cfg_attr(windows, link(name = "pythonXY"))]

View File

@ -1,5 +1,5 @@
use crate::ffi::object::*; use crate::object::*;
use crate::ffi::pyport::Py_ssize_t; use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
#[cfg_attr(windows, link(name = "pythonXY"))] #[cfg_attr(windows, link(name = "pythonXY"))]

View File

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

View File

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

View File

@ -1,4 +1,4 @@
use crate::ffi::object::*; use crate::object::*;
use std::os::raw::{c_double, c_int}; use std::os::raw::{c_double, c_int};
#[repr(C)] #[repr(C)]

View File

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

View File

@ -1,8 +1,8 @@
use crate::ffi::{PyObject, Py_buffer, Py_ssize_t}; use crate::{PyObject, Py_buffer, Py_ssize_t};
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
#[cfg(all(Py_3_8, not(PyPy)))] #[cfg(all(Py_3_8, not(PyPy)))]
use crate::ffi::{ use crate::{
pyport::PY_SSIZE_T_MAX, vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET, pyport::PY_SSIZE_T_MAX, vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET,
PyTuple_Check, PyType_HasFeature, Py_TPFLAGS_HAVE_VECTORCALL, PyTuple_Check, PyType_HasFeature, Py_TPFLAGS_HAVE_VECTORCALL,
}; };
@ -51,7 +51,7 @@ pub unsafe fn PyVectorcall_NARGS(n: size_t) -> Py_ssize_t {
#[inline(always)] #[inline(always)]
pub unsafe fn PyVectorcall_Function(callable: *mut PyObject) -> Option<vectorcallfunc> { pub unsafe fn PyVectorcall_Function(callable: *mut PyObject) -> Option<vectorcallfunc> {
assert!(!callable.is_null()); assert!(!callable.is_null());
let tp = crate::ffi::Py_TYPE(callable); let tp = crate::Py_TYPE(callable);
if PyType_HasFeature(tp, Py_TPFLAGS_HAVE_VECTORCALL) == 0 { if PyType_HasFeature(tp, Py_TPFLAGS_HAVE_VECTORCALL) == 0 {
return None; return None;
} }
@ -218,7 +218,7 @@ extern "C" {
#[cfg(not(any(Py_3_9, PyPy)))] #[cfg(not(any(Py_3_9, PyPy)))]
#[inline] #[inline]
pub unsafe fn PyObject_CheckBuffer(o: *mut PyObject) -> c_int { pub unsafe fn PyObject_CheckBuffer(o: *mut PyObject) -> c_int {
let tp_as_buffer = (*crate::ffi::Py_TYPE(o)).tp_as_buffer; let tp_as_buffer = (*crate::Py_TYPE(o)).tp_as_buffer;
(!tp_as_buffer.is_null() && (*tp_as_buffer).bf_getbuffer.is_some()) as c_int (!tp_as_buffer.is_null() && (*tp_as_buffer).bf_getbuffer.is_some()) as c_int
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
use crate::ffi::cpython::code::{PyCodeObject, CO_MAXBLOCKS}; use crate::cpython::code::{PyCodeObject, CO_MAXBLOCKS};
use crate::ffi::object::*; use crate::object::*;
use crate::ffi::pystate::PyThreadState; use crate::pystate::PyThreadState;
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
// skipped _framestate // skipped _framestate

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
use crate::ffi::PyObject; use crate::PyObject;
use std::os::raw::c_int; use std::os::raw::c_int;
// skipped _Py_NewReference // skipped _Py_NewReference
@ -12,7 +12,7 @@ use std::os::raw::c_int;
// skipped _Py_IDENTIFIER // skipped _Py_IDENTIFIER
mod bufferinfo { mod bufferinfo {
use crate::ffi::Py_ssize_t; use crate::Py_ssize_t;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::ptr; use std::ptr;
@ -24,7 +24,7 @@ mod bufferinfo {
pub struct Py_buffer { pub struct Py_buffer {
pub buf: *mut c_void, pub buf: *mut c_void,
/// Owned reference /// Owned reference
pub obj: *mut crate::ffi::PyObject, pub obj: *mut crate::PyObject,
pub len: Py_ssize_t, pub len: Py_ssize_t,
pub itemsize: Py_ssize_t, pub itemsize: Py_ssize_t,
pub readonly: c_int, pub readonly: c_int,
@ -67,12 +67,12 @@ mod bufferinfo {
} }
pub type getbufferproc = unsafe extern "C" fn( pub type getbufferproc = unsafe extern "C" fn(
arg1: *mut crate::ffi::PyObject, arg1: *mut crate::PyObject,
arg2: *mut Py_buffer, arg2: *mut Py_buffer,
arg3: c_int, arg3: c_int,
) -> c_int; ) -> c_int;
pub type releasebufferproc = pub type releasebufferproc =
unsafe extern "C" fn(arg1: *mut crate::ffi::PyObject, arg2: *mut Py_buffer); unsafe extern "C" fn(arg1: *mut crate::PyObject, arg2: *mut Py_buffer);
/// Maximum number of dimensions /// Maximum number of dimensions
pub const PyBUF_MAX_NDIM: c_int = 64; pub const PyBUF_MAX_NDIM: c_int = 64;
@ -117,8 +117,8 @@ pub type vectorcallfunc = unsafe extern "C" fn(
) -> *mut PyObject; ) -> *mut PyObject;
mod typeobject { mod typeobject {
use crate::ffi::{self, object}; use crate::object;
use crate::ffi::{PyObject, Py_ssize_t}; use crate::{PyObject, Py_ssize_t};
use std::mem; use std::mem;
use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void}; use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void};
@ -248,9 +248,9 @@ mod typeobject {
pub tp_weaklistoffset: Py_ssize_t, pub tp_weaklistoffset: Py_ssize_t,
pub tp_iter: Option<object::getiterfunc>, pub tp_iter: Option<object::getiterfunc>,
pub tp_iternext: Option<object::iternextfunc>, pub tp_iternext: Option<object::iternextfunc>,
pub tp_methods: *mut ffi::methodobject::PyMethodDef, pub tp_methods: *mut crate::methodobject::PyMethodDef,
pub tp_members: *mut ffi::structmember::PyMemberDef, pub tp_members: *mut crate::structmember::PyMemberDef,
pub tp_getset: *mut ffi::descrobject::PyGetSetDef, pub tp_getset: *mut crate::descrobject::PyGetSetDef,
pub tp_base: *mut PyTypeObject, pub tp_base: *mut PyTypeObject,
pub tp_dict: *mut object::PyObject, pub tp_dict: *mut object::PyObject,
pub tp_descr_get: Option<object::descrgetfunc>, pub tp_descr_get: Option<object::descrgetfunc>,
@ -310,10 +310,10 @@ mod typeobject {
#[inline] #[inline]
pub unsafe fn PyHeapType_GET_MEMBERS( pub unsafe fn PyHeapType_GET_MEMBERS(
etype: *mut PyHeapTypeObject, etype: *mut PyHeapTypeObject,
) -> *mut ffi::structmember::PyMemberDef { ) -> *mut crate::structmember::PyMemberDef {
let py_type = object::Py_TYPE(etype as *mut object::PyObject); let py_type = object::Py_TYPE(etype as *mut object::PyObject);
let ptr = etype.offset((*py_type).tp_basicsize); let ptr = etype.offset((*py_type).tp_basicsize);
ptr as *mut ffi::structmember::PyMemberDef ptr as *mut crate::structmember::PyMemberDef
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
use crate::ffi::object::*; use crate::object::*;
use crate::ffi::pyport::Py_ssize_t; use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
#[cfg_attr(windows, link(name = "pythonXY"))] #[cfg_attr(windows, link(name = "pythonXY"))]

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
use crate::ffi::object::*; use crate::object::*;
use std::os::raw::{c_double, c_int}; use std::os::raw::{c_double, c_int};
#[cfg(Py_LIMITED_API)] #[cfg(Py_LIMITED_API)]

View File

@ -1,6 +1,6 @@
use std::os::raw::c_int; use std::os::raw::c_int;
use crate::ffi::object::{PyObject, PyTypeObject, Py_TYPE}; use crate::object::{PyObject, PyTypeObject, Py_TYPE};
// skipped PyFunctionObject // skipped PyFunctionObject

View File

@ -1,6 +1,6 @@
use crate::ffi::object::*; use crate::object::*;
use crate::ffi::pyport::Py_ssize_t; use crate::pyport::Py_ssize_t;
use crate::ffi::PyFrameObject; use crate::PyFrameObject;
use std::os::raw::c_int; use std::os::raw::c_int;
#[repr(C)] #[repr(C)]

View File

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

View File

@ -1,4 +1,4 @@
use crate::ffi::object::*; use crate::object::*;
use std::os::raw::c_int; use std::os::raw::c_int;
#[cfg_attr(windows, link(name = "pythonXY"))] #[cfg_attr(windows, link(name = "pythonXY"))]

432
pyo3-ffi/src/lib.rs Normal file
View File

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

View File

@ -1,5 +1,5 @@
use crate::ffi::object::*; use crate::object::*;
use crate::ffi::pyport::Py_ssize_t; use crate::pyport::Py_ssize_t;
use std::os::raw::c_int; use std::os::raw::c_int;
#[cfg_attr(windows, link(name = "pythonXY"))] #[cfg_attr(windows, link(name = "pythonXY"))]

View File

@ -1,5 +1,5 @@
use crate::ffi::object::*; use crate::object::*;
use crate::ffi::pyport::Py_ssize_t; use crate::pyport::Py_ssize_t;
use libc::size_t; use libc::size_t;
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
use std::os::raw::c_uchar; use std::os::raw::c_uchar;

View File

@ -1,5 +1,5 @@
use crate::ffi::object::*; use crate::object::*;
use crate::ffi::pyport::Py_ssize_t; use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
// skipped non-limited _PyManagedBuffer_Type // skipped non-limited _PyManagedBuffer_Type

View File

@ -1,6 +1,6 @@
use crate::ffi::object::{PyObject, PyTypeObject, Py_TYPE}; use crate::object::{PyObject, PyTypeObject, Py_TYPE};
#[cfg(Py_3_9)] #[cfg(Py_3_9)]
use crate::ffi::PyObject_TypeCheck; use crate::PyObject_TypeCheck;
use std::mem; use std::mem;
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
@ -35,7 +35,7 @@ pub type PyCFunction =
pub type _PyCFunctionFast = unsafe extern "C" fn( pub type _PyCFunctionFast = unsafe extern "C" fn(
slf: *mut PyObject, slf: *mut PyObject,
args: *mut *mut PyObject, args: *mut *mut PyObject,
nargs: crate::ffi::pyport::Py_ssize_t, nargs: crate::pyport::Py_ssize_t,
kwnames: *mut PyObject, kwnames: *mut PyObject,
) -> *mut PyObject; ) -> *mut PyObject;
@ -49,7 +49,7 @@ pub type PyCFunctionWithKeywords = unsafe extern "C" fn(
pub type _PyCFunctionFastWithKeywords = unsafe extern "C" fn( pub type _PyCFunctionFastWithKeywords = unsafe extern "C" fn(
slf: *mut PyObject, slf: *mut PyObject,
args: *const *mut PyObject, args: *const *mut PyObject,
nargs: crate::ffi::pyport::Py_ssize_t, nargs: crate::pyport::Py_ssize_t,
kwnames: *mut PyObject, kwnames: *mut PyObject,
) -> *mut PyObject; ) -> *mut PyObject;

View File

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

View File

@ -1,6 +1,6 @@
use crate::ffi::methodobject::PyMethodDef; use crate::methodobject::PyMethodDef;
use crate::ffi::object::*; use crate::object::*;
use crate::ffi::pyport::Py_ssize_t; use crate::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
#[cfg_attr(windows, link(name = "pythonXY"))] #[cfg_attr(windows, link(name = "pythonXY"))]

View File

@ -1,6 +1,6 @@
// FFI note: this file changed a lot between 3.6 and 3.10. // FFI note: this file changed a lot between 3.6 and 3.10.
// Some missing definitions may not be marked "skipped". // Some missing definitions may not be marked "skipped".
use crate::ffi::pyport::{Py_hash_t, Py_ssize_t}; use crate::pyport::{Py_hash_t, Py_ssize_t};
use std::mem; use std::mem;
use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void}; use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void};
use std::ptr; use std::ptr;
@ -9,7 +9,7 @@ use std::ptr;
opaque_struct!(PyTypeObject); opaque_struct!(PyTypeObject);
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
pub use crate::ffi::cpython::object::PyTypeObject; pub use crate::cpython::object::PyTypeObject;
// _PyObject_HEAD_EXTRA: conditionally defined in PyObject_HEAD_INIT // _PyObject_HEAD_EXTRA: conditionally defined in PyObject_HEAD_INIT
// _PyObject_EXTRA_INIT: conditionally defined in PyObject_HEAD_INIT // _PyObject_EXTRA_INIT: conditionally defined in PyObject_HEAD_INIT

View File

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

View File

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

View File

@ -1,4 +1,4 @@
use crate::ffi::object::*; use crate::object::*;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
#[cfg_attr(windows, link(name = "pythonXY"))] #[cfg_attr(windows, link(name = "pythonXY"))]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
use crate::ffi::object::*; use crate::object::*;
use std::os::raw::c_int; use std::os::raw::c_int;
#[cfg_attr(windows, link(name = "pythonXY"))] #[cfg_attr(windows, link(name = "pythonXY"))]

View File

@ -1,7 +1,7 @@
use crate::ffi::object::*; use crate::object::*;
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
use crate::ffi::pyport::Py_hash_t; use crate::pyport::Py_hash_t;
use crate::ffi::pyport::Py_ssize_t; use crate::pyport::Py_ssize_t;
use std::os::raw::c_int; use std::os::raw::c_int;
pub const PySet_MINSIZE: usize = 8; pub const PySet_MINSIZE: usize = 8;
@ -120,7 +120,7 @@ pub unsafe fn PyAnySet_Check(ob: *mut PyObject) -> c_int {
#[inline] #[inline]
#[cfg(Py_3_10)] #[cfg(Py_3_10)]
pub unsafe fn PySet_CheckExact(op: *mut PyObject) -> c_int { pub unsafe fn PySet_CheckExact(op: *mut PyObject) -> c_int {
crate::ffi::Py_IS_TYPE(op, &mut PySet_Type) crate::Py_IS_TYPE(op, &mut PySet_Type)
} }
extern "C" { extern "C" {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
use crate::ffi::object::*; use crate::object::*;
use std::os::raw::c_int; use std::os::raw::c_int;
extern "C" { extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyTraceBack_Here")] #[cfg_attr(PyPy, link_name = "PyPyTraceBack_Here")]
pub fn PyTraceBack_Here(arg1: *mut crate::ffi::PyFrameObject) -> c_int; pub fn PyTraceBack_Here(arg1: *mut crate::PyFrameObject) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyTraceBack_Print")] #[cfg_attr(PyPy, link_name = "PyPyTraceBack_Print")]
pub fn PyTraceBack_Print(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; pub fn PyTraceBack_Print(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int;
} }

View File

@ -1,5 +1,5 @@
use crate::ffi::object::*; use crate::object::*;
use crate::ffi::pyport::Py_ssize_t; use crate::pyport::Py_ssize_t;
use std::os::raw::c_int; use std::os::raw::c_int;
#[cfg_attr(windows, link(name = "pythonXY"))] #[cfg_attr(windows, link(name = "pythonXY"))]

View File

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

View File

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

View File

@ -1,4 +1,4 @@
use crate::ffi::object::*; use crate::object::*;
use std::os::raw::c_int; use std::os::raw::c_int;
opaque_struct!(PyWeakReference); opaque_struct!(PyWeakReference);

View File

@ -1,15 +0,0 @@
rust python3 ffi
================
[Rust](https://www.rust-lang.org/) FFI declarations for Python 3.
Supports the PEP 384 stable ABI for Python 3.4 or higher.
---
This [cargo -sys package](https://doc.rust-lang.org/cargo/reference/build-scripts.html#-sys-packages) provides `python3` declarations.
Licensed under the Python license (see `LICENSE`).
For a safe high-level API, see [PyO3](https://github.com/PyO3/PyO3).
Documentation for the Python API is available on [https://docs.python.org/3/c-api/].

View File

@ -20,190 +20,12 @@
//! for more information. //! for more information.
//! //!
//! [capi]: https://docs.python.org/3/c-api/index.html //! [capi]: https://docs.python.org/3/c-api/index.html
#![allow(
missing_docs,
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
clippy::upper_case_acronyms,
clippy::missing_safety_doc
)]
// Until `extern type` is stabilized, use the recommended approach to #[cfg(all(not(Py_LIMITED_API), test))]
// model opaque types: mod tests;
// https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs
macro_rules! opaque_struct {
($name:ident) => {
#[repr(C)]
pub struct $name([u8; 0]);
};
}
pub use self::abstract_::*; // reexport raw bindings exposed in pyo3_ffi
pub use self::bltinmodule::*; pub use pyo3_ffi::*;
pub use self::boolobject::*;
pub use self::bytearrayobject::*;
pub use self::bytesobject::*;
pub use self::ceval::*;
pub use self::code::*;
pub use self::codecs::*;
pub use self::compile::*;
pub use self::complexobject::*;
#[cfg(all(Py_3_8, not(Py_LIMITED_API)))]
pub use self::context::*;
#[cfg(not(Py_LIMITED_API))]
pub use self::datetime::*;
pub use self::descrobject::*;
pub use self::dictobject::*;
pub use self::enumobject::*;
pub use self::eval::*;
pub use self::fileobject::*;
pub use self::fileutils::*;
pub use self::floatobject::*;
#[cfg(not(Py_LIMITED_API))]
pub use self::funcobject::*;
#[cfg(not(Py_LIMITED_API))]
pub use self::genobject::*;
pub use self::import::*;
pub use self::intrcheck::*;
pub use self::iterobject::*;
pub use self::listobject::*;
pub use self::longobject::*;
pub use self::marshal::*;
pub use self::memoryobject::*;
pub use self::methodobject::*;
pub use self::modsupport::*;
pub use self::moduleobject::*;
pub use self::object::*;
pub use self::objimpl::*;
pub use self::osmodule::*;
#[cfg(not(Py_LIMITED_API))]
pub use self::pyarena::*;
pub use self::pycapsule::*;
pub use self::pyerrors::*;
pub use self::pyframe::*;
pub use self::pyhash::*;
pub use self::pylifecycle::*;
pub use self::pymem::*;
pub use self::pyport::*;
pub use self::pystate::*;
pub use self::pystrtod::*;
pub use self::pythonrun::*;
pub use self::rangeobject::*;
pub use self::setobject::*;
pub use self::sliceobject::*;
pub use self::structseq::*;
pub use self::sysmodule::*;
pub use self::traceback::*;
pub use self::tupleobject::*;
pub use self::typeslots::*;
pub use self::unicodeobject::*;
pub use self::warnings::*;
pub use self::weakrefobject::*;
mod abstract_;
// skipped asdl.h
// skipped ast.h
mod bltinmodule;
mod boolobject;
mod bytearrayobject;
mod bytesobject;
// skipped cellobject.h
mod ceval;
// skipped classobject.h
mod code;
mod codecs;
mod compile;
mod complexobject;
#[cfg(all(Py_3_8, not(Py_LIMITED_API)))]
mod context; // It's actually 3.7.1, but no cfg for patches.
#[cfg(not(Py_LIMITED_API))]
pub(crate) mod datetime;
mod descrobject;
mod dictobject;
// skipped dynamic_annotations.h
mod enumobject;
// skipped errcode.h
mod eval;
// skipped exports.h
mod fileobject;
mod fileutils;
mod floatobject;
// skipped empty frameobject.h
#[cfg(not(Py_LIMITED_API))]
pub(crate) mod funcobject;
// skipped genericaliasobject.h
#[cfg(not(Py_LIMITED_API))]
mod genobject;
mod import;
// skipped interpreteridobject.h
mod intrcheck;
mod iterobject;
mod listobject;
// skipped longintrepr.h
mod longobject;
pub(crate) mod marshal;
mod memoryobject;
mod methodobject;
mod modsupport;
mod moduleobject;
// skipped namespaceobject.h
mod object;
mod objimpl;
// skipped odictobject.h
// skipped opcode.h
// skipped osdefs.h
mod osmodule;
// skipped parser_interface.h
// skipped patchlevel.h
// skipped picklebufobject.h
// skipped pyctype.h
// skipped py_curses.h
#[cfg(not(Py_LIMITED_API))]
mod pyarena;
mod pycapsule;
// skipped pydecimal.h
// skipped pydtrace.h
mod pyerrors;
// skipped pyexpat.h
// skipped pyfpe.h
mod pyframe;
mod pyhash;
mod pylifecycle;
// skipped pymacconfig.h
// skipped pymacro.h
// skipped pymath.h
mod pymem;
mod pyport;
mod pystate;
mod pythonrun;
// skipped pystrhex.h
// skipped pystrcmp.h
mod pystrtod;
// skipped pythread.h
// skipped pytime.h
mod rangeobject;
mod setobject;
mod sliceobject;
mod structseq;
mod sysmodule;
mod traceback;
// skipped tracemalloc.h
mod tupleobject;
mod typeslots;
mod unicodeobject;
mod warnings;
mod weakrefobject;
// Additional headers that are not exported by Python.h
pub mod structmember;
// "Limited API" definitions matching Python's `include/cpython` directory.
#[cfg(not(Py_LIMITED_API))]
mod cpython;
#[cfg(not(Py_LIMITED_API))]
pub use self::cpython::*;
/// Helper to enable #\[pymethods\] to see the workaround for __ipow__ on Python 3.7 /// Helper to enable #\[pymethods\] to see the workaround for __ipow__ on Python 3.7
#[doc(hidden)] #[doc(hidden)]

183
src/ffi/tests.rs Normal file
View File

@ -0,0 +1,183 @@
use crate::ffi::*;
use crate::{types::PyDict, AsPyPointer, IntoPy, Py, PyAny, Python};
#[cfg(target_endian = "little")]
use crate::types::PyString;
#[cfg(target_endian = "little")]
use libc::wchar_t;
#[test]
fn test_datetime_fromtimestamp() {
Python::with_gil(|py| {
let args: Py<PyAny> = (100,).into_py(py);
unsafe { PyDateTime_IMPORT() };
let dt: &PyAny = unsafe { py.from_owned_ptr(PyDateTime_FromTimestamp(args.as_ptr())) };
let locals = PyDict::new(py);
locals.set_item("dt", dt).unwrap();
py.run(
"import datetime; assert dt == datetime.datetime.fromtimestamp(100)",
None,
Some(locals),
)
.unwrap();
})
}
#[test]
fn test_date_fromtimestamp() {
Python::with_gil(|py| {
let args: Py<PyAny> = (100,).into_py(py);
unsafe { PyDateTime_IMPORT() };
let dt: &PyAny = unsafe { py.from_owned_ptr(PyDate_FromTimestamp(args.as_ptr())) };
let locals = PyDict::new(py);
locals.set_item("dt", dt).unwrap();
py.run(
"import datetime; assert dt == datetime.date.fromtimestamp(100)",
None,
Some(locals),
)
.unwrap();
})
}
#[test]
#[cfg(not(all(PyPy, not(Py_3_8))))]
fn test_utc_timezone() {
Python::with_gil(|py| {
let utc_timezone = unsafe {
PyDateTime_IMPORT();
&*(&PyDateTime_TimeZone_UTC() as *const *mut crate::ffi::PyObject
as *const crate::PyObject)
};
let locals = PyDict::new(py);
locals.set_item("utc_timezone", utc_timezone).unwrap();
py.run(
"import datetime; assert utc_timezone is datetime.timezone.utc",
None,
Some(locals),
)
.unwrap();
})
}
#[cfg(target_endian = "little")]
#[test]
fn ascii_object_bitfield() {
let ob_base: PyObject = unsafe { std::mem::zeroed() };
let mut o = PyASCIIObject {
ob_base,
length: 0,
hash: 0,
state: 0,
wstr: std::ptr::null_mut() as *mut wchar_t,
};
unsafe {
assert_eq!(o.interned(), 0);
assert_eq!(o.kind(), 0);
assert_eq!(o.compact(), 0);
assert_eq!(o.ascii(), 0);
assert_eq!(o.ready(), 0);
for i in 0..4 {
o.state = i;
assert_eq!(o.interned(), i);
}
for i in 0..8 {
o.state = i << 2;
assert_eq!(o.kind(), i);
}
o.state = 1 << 5;
assert_eq!(o.compact(), 1);
o.state = 1 << 6;
assert_eq!(o.ascii(), 1);
o.state = 1 << 7;
assert_eq!(o.ready(), 1);
}
}
#[cfg(target_endian = "little")]
#[test]
#[cfg_attr(Py_3_10, allow(deprecated))]
fn ascii() {
Python::with_gil(|py| {
// This test relies on implementation details of PyString.
let s = PyString::new(py, "hello, world");
let ptr = s.as_ptr();
unsafe {
let ascii_ptr = ptr as *mut PyASCIIObject;
let ascii = ascii_ptr.as_ref().unwrap();
assert_eq!(ascii.interned(), 0);
assert_eq!(ascii.kind(), PyUnicode_1BYTE_KIND);
assert_eq!(ascii.compact(), 1);
assert_eq!(ascii.ascii(), 1);
assert_eq!(ascii.ready(), 1);
assert_eq!(PyUnicode_IS_ASCII(ptr), 1);
assert_eq!(PyUnicode_IS_COMPACT(ptr), 1);
assert_eq!(PyUnicode_IS_COMPACT_ASCII(ptr), 1);
assert!(!PyUnicode_1BYTE_DATA(ptr).is_null());
// 2 and 4 byte macros return nonsense for this string instance.
assert_eq!(PyUnicode_KIND(ptr), PyUnicode_1BYTE_KIND);
assert!(!_PyUnicode_COMPACT_DATA(ptr).is_null());
// _PyUnicode_NONCOMPACT_DATA isn't valid for compact strings.
assert!(!PyUnicode_DATA(ptr).is_null());
assert_eq!(PyUnicode_GET_LENGTH(ptr), s.len().unwrap() as _);
assert_eq!(PyUnicode_IS_READY(ptr), 1);
// This has potential to mutate object. But it should be a no-op since
// we're already ready.
assert_eq!(PyUnicode_READY(ptr), 0);
}
})
}
#[cfg(target_endian = "little")]
#[test]
#[cfg_attr(Py_3_10, allow(deprecated))]
fn ucs4() {
Python::with_gil(|py| {
let s = "哈哈🐈";
let py_string = PyString::new(py, s);
let ptr = py_string.as_ptr();
unsafe {
let ascii_ptr = ptr as *mut PyASCIIObject;
let ascii = ascii_ptr.as_ref().unwrap();
assert_eq!(ascii.interned(), 0);
assert_eq!(ascii.kind(), PyUnicode_4BYTE_KIND);
assert_eq!(ascii.compact(), 1);
assert_eq!(ascii.ascii(), 0);
assert_eq!(ascii.ready(), 1);
assert_eq!(PyUnicode_IS_ASCII(ptr), 0);
assert_eq!(PyUnicode_IS_COMPACT(ptr), 1);
assert_eq!(PyUnicode_IS_COMPACT_ASCII(ptr), 0);
assert!(!PyUnicode_4BYTE_DATA(ptr).is_null());
assert_eq!(PyUnicode_KIND(ptr), PyUnicode_4BYTE_KIND);
assert!(!_PyUnicode_COMPACT_DATA(ptr).is_null());
// _PyUnicode_NONCOMPACT_DATA isn't valid for compact strings.
assert!(!PyUnicode_DATA(ptr).is_null());
assert_eq!(PyUnicode_GET_LENGTH(ptr), py_string.len().unwrap() as _);
assert_eq!(PyUnicode_IS_READY(ptr), 1);
// This has potential to mutate object. But it should be a no-op since
// we're already ready.
assert_eq!(PyUnicode_READY(ptr), 0);
}
})
}

View File

@ -5,10 +5,9 @@
use crate::err::PyResult; use crate::err::PyResult;
use crate::ffi; use crate::ffi;
#[cfg(PyPy)] use crate::ffi::{
use crate::ffi::datetime::{PyDateTime_FromTimestamp, PyDate_FromTimestamp}; PyDateTime_CAPI, PyDateTime_FromTimestamp, PyDateTime_IMPORT, PyDate_FromTimestamp,
use crate::ffi::PyDateTimeAPI; };
use crate::ffi::{PyDateTime_Check, PyDate_Check, PyDelta_Check, PyTZInfo_Check, PyTime_Check};
#[cfg(not(PyPy))] #[cfg(not(PyPy))]
use crate::ffi::{PyDateTime_DATE_GET_FOLD, PyDateTime_TIME_GET_FOLD}; use crate::ffi::{PyDateTime_DATE_GET_FOLD, PyDateTime_TIME_GET_FOLD};
use crate::ffi::{ use crate::ffi::{
@ -26,8 +25,64 @@ use crate::ffi::{
use crate::types::PyTuple; use crate::types::PyTuple;
use crate::{AsPyPointer, PyAny, PyObject, Python, ToPyObject}; use crate::{AsPyPointer, PyAny, PyObject, Python, ToPyObject};
use std::os::raw::c_int; use std::os::raw::c_int;
#[cfg(not(PyPy))]
use std::ptr; fn ensure_datetime_api(_py: Python) -> &'static PyDateTime_CAPI {
unsafe {
if pyo3_ffi::PyDateTimeAPI().is_null() {
PyDateTime_IMPORT()
}
&*pyo3_ffi::PyDateTimeAPI()
}
}
// Type Check macros
//
// These are bindings around the C API typecheck macros, all of them return
// `1` if True and `0` if False. In all type check macros, the argument (`op`)
// must not be `NULL`. The implementations here all call ensure_datetime_api
// to ensure that the PyDateTimeAPI is initalized before use
//
//
// # Safety
//
// These functions must only be called when the GIL is held!
macro_rules! ffi_fun_with_autoinit {
($(#[$outer:meta] unsafe fn $name: ident($arg: ident: *mut PyObject) -> $ret: ty;)*) => {
$(
#[$outer]
#[allow(non_snake_case)]
/// # Safety
///
/// Must only be called while the GIL is held
unsafe fn $name($arg: *mut crate::ffi::PyObject) -> $ret {
let _ = ensure_datetime_api(Python::assume_gil_acquired());
crate::ffi::$name($arg)
}
)*
};
}
ffi_fun_with_autoinit! {
/// Check if `op` is a `PyDateTimeAPI.DateType` or subtype.
unsafe fn PyDate_Check(op: *mut PyObject) -> c_int;
/// Check if `op` is a `PyDateTimeAPI.DateTimeType` or subtype.
unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int;
/// Check if `op` is a `PyDateTimeAPI.TimeType` or subtype.
unsafe fn PyTime_Check(op: *mut PyObject) -> c_int;
/// Check if `op` is a `PyDateTimeAPI.DetaType` or subtype.
unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int;
/// Check if `op` is a `PyDateTimeAPI.TZInfoType` or subtype.
unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int;
}
// Access traits // Access traits
@ -111,7 +166,7 @@ pub struct PyDate(PyAny);
pyobject_native_type!( pyobject_native_type!(
PyDate, PyDate,
crate::ffi::PyDateTime_Date, crate::ffi::PyDateTime_Date,
*PyDateTimeAPI.DateType, *ensure_datetime_api(Python::assume_gil_acquired()).DateType,
#module=Some("datetime"), #module=Some("datetime"),
#checkfunction=PyDate_Check #checkfunction=PyDate_Check
); );
@ -120,11 +175,11 @@ impl PyDate {
/// Creates a new `datetime.date`. /// Creates a new `datetime.date`.
pub fn new(py: Python, year: i32, month: u8, day: u8) -> PyResult<&PyDate> { pub fn new(py: Python, year: i32, month: u8, day: u8) -> PyResult<&PyDate> {
unsafe { unsafe {
let ptr = (PyDateTimeAPI.Date_FromDate)( let ptr = (ensure_datetime_api(py).Date_FromDate)(
year, year,
c_int::from(month), c_int::from(month),
c_int::from(day), c_int::from(day),
PyDateTimeAPI.DateType, ensure_datetime_api(py).DateType,
); );
py.from_owned_ptr_or_err(ptr) py.from_owned_ptr_or_err(ptr)
} }
@ -136,14 +191,11 @@ impl PyDate {
pub fn from_timestamp(py: Python, timestamp: i64) -> PyResult<&PyDate> { pub fn from_timestamp(py: Python, timestamp: i64) -> PyResult<&PyDate> {
let time_tuple = PyTuple::new(py, &[timestamp]); let time_tuple = PyTuple::new(py, &[timestamp]);
// safety ensure that the API is loaded
let _api = ensure_datetime_api(py);
unsafe { unsafe {
#[cfg(PyPy)]
let ptr = PyDate_FromTimestamp(time_tuple.as_ptr()); let ptr = PyDate_FromTimestamp(time_tuple.as_ptr());
#[cfg(not(PyPy))]
let ptr =
(PyDateTimeAPI.Date_FromTimestamp)(PyDateTimeAPI.DateType, time_tuple.as_ptr());
py.from_owned_ptr_or_err(ptr) py.from_owned_ptr_or_err(ptr)
} }
} }
@ -169,7 +221,7 @@ pub struct PyDateTime(PyAny);
pyobject_native_type!( pyobject_native_type!(
PyDateTime, PyDateTime,
crate::ffi::PyDateTime_DateTime, crate::ffi::PyDateTime_DateTime,
*PyDateTimeAPI.DateTimeType, *ensure_datetime_api(Python::assume_gil_acquired()).DateType,
#module=Some("datetime"), #module=Some("datetime"),
#checkfunction=PyDateTime_Check #checkfunction=PyDateTime_Check
); );
@ -187,8 +239,9 @@ impl PyDateTime {
microsecond: u32, microsecond: u32,
tzinfo: Option<&PyObject>, tzinfo: Option<&PyObject>,
) -> PyResult<&'p PyDateTime> { ) -> PyResult<&'p PyDateTime> {
let api = ensure_datetime_api(py);
unsafe { unsafe {
let ptr = (PyDateTimeAPI.DateTime_FromDateAndTime)( let ptr = (api.DateTime_FromDateAndTime)(
year, year,
c_int::from(month), c_int::from(month),
c_int::from(day), c_int::from(day),
@ -197,7 +250,7 @@ impl PyDateTime {
c_int::from(second), c_int::from(second),
microsecond as c_int, microsecond as c_int,
opt_to_pyobj(py, tzinfo), opt_to_pyobj(py, tzinfo),
PyDateTimeAPI.DateTimeType, api.DateTimeType,
); );
py.from_owned_ptr_or_err(ptr) py.from_owned_ptr_or_err(ptr)
} }
@ -219,8 +272,9 @@ impl PyDateTime {
tzinfo: Option<&PyObject>, tzinfo: Option<&PyObject>,
fold: bool, fold: bool,
) -> PyResult<&'p PyDateTime> { ) -> PyResult<&'p PyDateTime> {
let api = ensure_datetime_api(py);
unsafe { unsafe {
let ptr = (PyDateTimeAPI.DateTime_FromDateAndTimeAndFold)( let ptr = (api.DateTime_FromDateAndTimeAndFold)(
year, year,
c_int::from(month), c_int::from(month),
c_int::from(day), c_int::from(day),
@ -230,7 +284,7 @@ impl PyDateTime {
microsecond as c_int, microsecond as c_int,
opt_to_pyobj(py, tzinfo), opt_to_pyobj(py, tzinfo),
c_int::from(fold), c_int::from(fold),
PyDateTimeAPI.DateTimeType, api.DateTimeType,
); );
py.from_owned_ptr_or_err(ptr) py.from_owned_ptr_or_err(ptr)
} }
@ -253,19 +307,11 @@ impl PyDateTime {
let args = PyTuple::new(py, &[timestamp, time_zone_info]); let args = PyTuple::new(py, &[timestamp, time_zone_info]);
// safety ensure API is loaded
let _api = ensure_datetime_api(py);
unsafe { unsafe {
#[cfg(PyPy)]
let ptr = PyDateTime_FromTimestamp(args.as_ptr()); let ptr = PyDateTime_FromTimestamp(args.as_ptr());
#[cfg(not(PyPy))]
let ptr = {
(PyDateTimeAPI.DateTime_FromTimestamp)(
PyDateTimeAPI.DateTimeType,
args.as_ptr(),
ptr::null_mut(),
)
};
py.from_owned_ptr_or_err(ptr) py.from_owned_ptr_or_err(ptr)
} }
} }
@ -314,7 +360,7 @@ pub struct PyTime(PyAny);
pyobject_native_type!( pyobject_native_type!(
PyTime, PyTime,
crate::ffi::PyDateTime_Time, crate::ffi::PyDateTime_Time,
*PyDateTimeAPI.TimeType, *ensure_datetime_api(Python::assume_gil_acquired()).TimeType,
#module=Some("datetime"), #module=Some("datetime"),
#checkfunction=PyTime_Check #checkfunction=PyTime_Check
); );
@ -329,14 +375,15 @@ impl PyTime {
microsecond: u32, microsecond: u32,
tzinfo: Option<&PyObject>, tzinfo: Option<&PyObject>,
) -> PyResult<&'p PyTime> { ) -> PyResult<&'p PyTime> {
let api = ensure_datetime_api(py);
unsafe { unsafe {
let ptr = (PyDateTimeAPI.Time_FromTime)( let ptr = (api.Time_FromTime)(
c_int::from(hour), c_int::from(hour),
c_int::from(minute), c_int::from(minute),
c_int::from(second), c_int::from(second),
microsecond as c_int, microsecond as c_int,
opt_to_pyobj(py, tzinfo), opt_to_pyobj(py, tzinfo),
PyDateTimeAPI.TimeType, api.TimeType,
); );
py.from_owned_ptr_or_err(ptr) py.from_owned_ptr_or_err(ptr)
} }
@ -353,15 +400,16 @@ impl PyTime {
tzinfo: Option<&PyObject>, tzinfo: Option<&PyObject>,
fold: bool, fold: bool,
) -> PyResult<&'p PyTime> { ) -> PyResult<&'p PyTime> {
let api = ensure_datetime_api(py);
unsafe { unsafe {
let ptr = (PyDateTimeAPI.Time_FromTimeAndFold)( let ptr = (api.Time_FromTimeAndFold)(
c_int::from(hour), c_int::from(hour),
c_int::from(minute), c_int::from(minute),
c_int::from(second), c_int::from(second),
microsecond as c_int, microsecond as c_int,
opt_to_pyobj(py, tzinfo), opt_to_pyobj(py, tzinfo),
fold as c_int, fold as c_int,
PyDateTimeAPI.TimeType, api.TimeType,
); );
py.from_owned_ptr_or_err(ptr) py.from_owned_ptr_or_err(ptr)
} }
@ -399,7 +447,7 @@ pub struct PyTzInfo(PyAny);
pyobject_native_type!( pyobject_native_type!(
PyTzInfo, PyTzInfo,
crate::ffi::PyObject, crate::ffi::PyObject,
*PyDateTimeAPI.TZInfoType, *ensure_datetime_api(Python::assume_gil_acquired()).TZInfoType,
#module=Some("datetime"), #module=Some("datetime"),
#checkfunction=PyTZInfo_Check #checkfunction=PyTZInfo_Check
); );
@ -410,7 +458,7 @@ pub struct PyDelta(PyAny);
pyobject_native_type!( pyobject_native_type!(
PyDelta, PyDelta,
crate::ffi::PyDateTime_Delta, crate::ffi::PyDateTime_Delta,
*PyDateTimeAPI.DeltaType, *ensure_datetime_api(Python::assume_gil_acquired()).DeltaType,
#module=Some("datetime"), #module=Some("datetime"),
#checkfunction=PyDelta_Check #checkfunction=PyDelta_Check
); );
@ -424,13 +472,14 @@ impl PyDelta {
microseconds: i32, microseconds: i32,
normalize: bool, normalize: bool,
) -> PyResult<&PyDelta> { ) -> PyResult<&PyDelta> {
let api = ensure_datetime_api(py);
unsafe { unsafe {
let ptr = (PyDateTimeAPI.Delta_FromDelta)( let ptr = (api.Delta_FromDelta)(
days as c_int, days as c_int,
seconds as c_int, seconds as c_int,
microseconds as c_int, microseconds as c_int,
normalize as c_int, normalize as c_int,
PyDateTimeAPI.DeltaType, api.DeltaType,
); );
py.from_owned_ptr_or_err(ptr) py.from_owned_ptr_or_err(ptr)
} }

View File

@ -2,6 +2,7 @@
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::IntoPyDict; use pyo3::types::IntoPyDict;
use pyo3_ffi::PyDateTime_IMPORT;
fn _get_subclasses<'p>( fn _get_subclasses<'p>(
py: &'p Python, py: &'p Python,
@ -57,7 +58,7 @@ fn test_date_check() {
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
let py = gil.python(); let py = gil.python();
let (obj, sub_obj, sub_sub_obj) = _get_subclasses(&py, "date", "2018, 1, 1").unwrap(); let (obj, sub_obj, sub_sub_obj) = _get_subclasses(&py, "date", "2018, 1, 1").unwrap();
unsafe { PyDateTime_IMPORT() }
assert_check_exact!(PyDate_Check, PyDate_CheckExact, obj); assert_check_exact!(PyDate_Check, PyDate_CheckExact, obj);
assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_obj); assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_obj);
assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_sub_obj); assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_sub_obj);
@ -68,6 +69,7 @@ fn test_time_check() {
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
let py = gil.python(); let py = gil.python();
let (obj, sub_obj, sub_sub_obj) = _get_subclasses(&py, "time", "12, 30, 15").unwrap(); let (obj, sub_obj, sub_sub_obj) = _get_subclasses(&py, "time", "12, 30, 15").unwrap();
unsafe { PyDateTime_IMPORT() }
assert_check_exact!(PyTime_Check, PyTime_CheckExact, obj); assert_check_exact!(PyTime_Check, PyTime_CheckExact, obj);
assert_check_only!(PyTime_Check, PyTime_CheckExact, sub_obj); assert_check_only!(PyTime_Check, PyTime_CheckExact, sub_obj);
@ -81,6 +83,7 @@ fn test_datetime_check() {
let (obj, sub_obj, sub_sub_obj) = _get_subclasses(&py, "datetime", "2018, 1, 1, 13, 30, 15") let (obj, sub_obj, sub_sub_obj) = _get_subclasses(&py, "datetime", "2018, 1, 1, 13, 30, 15")
.map_err(|e| e.print(py)) .map_err(|e| e.print(py))
.unwrap(); .unwrap();
unsafe { PyDateTime_IMPORT() }
assert_check_only!(PyDate_Check, PyDate_CheckExact, obj); assert_check_only!(PyDate_Check, PyDate_CheckExact, obj);
assert_check_exact!(PyDateTime_Check, PyDateTime_CheckExact, obj); assert_check_exact!(PyDateTime_Check, PyDateTime_CheckExact, obj);
@ -93,6 +96,7 @@ fn test_delta_check() {
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
let py = gil.python(); let py = gil.python();
let (obj, sub_obj, sub_sub_obj) = _get_subclasses(&py, "timedelta", "1, -3").unwrap(); let (obj, sub_obj, sub_sub_obj) = _get_subclasses(&py, "timedelta", "1, -3").unwrap();
unsafe { PyDateTime_IMPORT() }
assert_check_exact!(PyDelta_Check, PyDelta_CheckExact, obj); assert_check_exact!(PyDelta_Check, PyDelta_CheckExact, obj);
assert_check_only!(PyDelta_Check, PyDelta_CheckExact, sub_obj); assert_check_only!(PyDelta_Check, PyDelta_CheckExact, sub_obj);

View File

@ -107,6 +107,7 @@ fn llvm_cov_command(args: &[&str]) -> Command {
"--package=pyo3-build-config", "--package=pyo3-build-config",
"--package=pyo3-macros-backend", "--package=pyo3-macros-backend",
"--package=pyo3-macros", "--package=pyo3-macros",
"--package=pyo3-ffi",
]) ])
.args(args); .args(args);
command command