From 6a9a9ba38a3f7c2010474ddc1d4b8da614ce48cd Mon Sep 17 00:00:00 2001 From: DSPOM Date: Tue, 25 Jan 2022 14:46:27 +0100 Subject: [PATCH 1/2] move ffi module to separate crate --- Architecture.md | 7 +- CHANGELOG.md | 17 + Cargo.toml | 16 +- build.rs | 103 +---- pyo3-ffi/Cargo.toml | 39 ++ {src/ffi => pyo3-ffi}/LICENSE | 0 pyo3-ffi/README.md | 201 ++++++++ pyo3-ffi/build.rs | 119 +++++ {src/ffi => pyo3-ffi/src}/abstract_.rs | 9 +- {src/ffi => pyo3-ffi/src}/bltinmodule.rs | 2 +- {src/ffi => pyo3-ffi/src}/boolobject.rs | 4 +- {src/ffi => pyo3-ffi/src}/bytearrayobject.rs | 4 +- {src/ffi => pyo3-ffi/src}/bytesobject.rs | 4 +- {src/ffi => pyo3-ffi/src}/ceval.rs | 10 +- {src/ffi => pyo3-ffi/src}/code.rs | 0 {src/ffi => pyo3-ffi/src}/codecs.rs | 2 +- {src/ffi => pyo3-ffi/src}/compile.rs | 0 {src/ffi => pyo3-ffi/src}/complexobject.rs | 2 +- {src/ffi => pyo3-ffi/src}/context.rs | 2 +- .../ffi => pyo3-ffi/src}/cpython/abstract_.rs | 8 +- .../src}/cpython/bytesobject.rs | 4 +- {src/ffi => pyo3-ffi/src}/cpython/ceval.rs | 9 +- {src/ffi => pyo3-ffi/src}/cpython/code.rs | 6 +- {src/ffi => pyo3-ffi/src}/cpython/compile.rs | 8 +- .../src}/cpython/dictobject.rs | 8 +- .../src}/cpython/frameobject.rs | 6 +- {src/ffi => pyo3-ffi/src}/cpython/import.rs | 2 +- .../src}/cpython/initconfig.rs | 2 +- .../src}/cpython/listobject.rs | 4 +- {src/ffi => pyo3-ffi/src}/cpython/mod.rs | 0 {src/ffi => pyo3-ffi/src}/cpython/object.rs | 24 +- {src/ffi => pyo3-ffi/src}/cpython/pydebug.rs | 0 .../src}/cpython/pylifecycle.rs | 2 +- {src/ffi => pyo3-ffi/src}/cpython/pymem.rs | 0 {src/ffi => pyo3-ffi/src}/cpython/pystate.rs | 10 +- .../ffi => pyo3-ffi/src}/cpython/pythonrun.rs | 8 +- .../src}/cpython/tupleobject.rs | 4 +- .../src}/cpython/unicodeobject.rs | 139 +----- {src/ffi => pyo3-ffi/src}/datetime.rs | 203 ++------ {src/ffi => pyo3-ffi/src}/descrobject.rs | 6 +- {src/ffi => pyo3-ffi/src}/dictobject.rs | 4 +- {src/ffi => pyo3-ffi/src}/enumobject.rs | 2 +- {src/ffi => pyo3-ffi/src}/eval.rs | 2 +- {src/ffi => pyo3-ffi/src}/fileobject.rs | 2 +- {src/ffi => pyo3-ffi/src}/fileutils.rs | 2 +- {src/ffi => pyo3-ffi/src}/floatobject.rs | 2 +- {src/ffi => pyo3-ffi/src}/funcobject.rs | 2 +- {src/ffi => pyo3-ffi/src}/genobject.rs | 6 +- {src/ffi => pyo3-ffi/src}/import.rs | 2 +- {src/ffi => pyo3-ffi/src}/intrcheck.rs | 0 {src/ffi => pyo3-ffi/src}/iterobject.rs | 2 +- pyo3-ffi/src/lib.rs | 432 ++++++++++++++++++ {src/ffi => pyo3-ffi/src}/listobject.rs | 4 +- {src/ffi => pyo3-ffi/src}/longobject.rs | 4 +- {src/ffi => pyo3-ffi/src}/marshal.rs | 0 {src/ffi => pyo3-ffi/src}/memoryobject.rs | 4 +- {src/ffi => pyo3-ffi/src}/methodobject.rs | 8 +- {src/ffi => pyo3-ffi/src}/modsupport.rs | 8 +- {src/ffi => pyo3-ffi/src}/moduleobject.rs | 6 +- {src/ffi => pyo3-ffi/src}/object.rs | 4 +- {src/ffi => pyo3-ffi/src}/objimpl.rs | 4 +- {src/ffi => pyo3-ffi/src}/osmodule.rs | 2 +- {src/ffi => pyo3-ffi/src}/pyarena.rs | 0 {src/ffi => pyo3-ffi/src}/pycapsule.rs | 2 +- {src/ffi => pyo3-ffi/src}/pyerrors.rs | 6 +- {src/ffi => pyo3-ffi/src}/pyframe.rs | 2 +- {src/ffi => pyo3-ffi/src}/pyhash.rs | 2 +- {src/ffi => pyo3-ffi/src}/pylifecycle.rs | 2 +- {src/ffi => pyo3-ffi/src}/pymem.rs | 0 {src/ffi => pyo3-ffi/src}/pyport.rs | 0 {src/ffi => pyo3-ffi/src}/pystate.rs | 4 +- {src/ffi => pyo3-ffi/src}/pystrtod.rs | 2 +- {src/ffi => pyo3-ffi/src}/pythonrun.rs | 6 +- {src/ffi => pyo3-ffi/src}/rangeobject.rs | 2 +- {src/ffi => pyo3-ffi/src}/setobject.rs | 8 +- {src/ffi => pyo3-ffi/src}/sliceobject.rs | 4 +- {src/ffi => pyo3-ffi/src}/structmember.rs | 4 +- {src/ffi => pyo3-ffi/src}/structseq.rs | 10 +- {src/ffi => pyo3-ffi/src}/sysmodule.rs | 2 +- {src/ffi => pyo3-ffi/src}/traceback.rs | 4 +- {src/ffi => pyo3-ffi/src}/tupleobject.rs | 4 +- {src/ffi => pyo3-ffi/src}/typeslots.rs | 0 {src/ffi => pyo3-ffi/src}/unicodeobject.rs | 4 +- {src/ffi => pyo3-ffi/src}/warnings.rs | 4 +- {src/ffi => pyo3-ffi/src}/weakrefobject.rs | 2 +- src/ffi/README.md | 15 - src/ffi/mod.rs | 186 +------- src/ffi/tests.rs | 183 ++++++++ src/types/datetime.rs | 129 ++++-- tests/test_datetime.rs | 6 +- xtask/src/main.rs | 1 + 91 files changed, 1307 insertions(+), 783 deletions(-) create mode 100644 pyo3-ffi/Cargo.toml rename {src/ffi => pyo3-ffi}/LICENSE (100%) create mode 100644 pyo3-ffi/README.md create mode 100644 pyo3-ffi/build.rs rename {src/ffi => pyo3-ffi/src}/abstract_.rs (98%) rename {src/ffi => pyo3-ffi/src}/bltinmodule.rs (84%) rename {src/ffi => pyo3-ffi/src}/boolobject.rs (94%) rename {src/ffi => pyo3-ffi/src}/bytearrayobject.rs (95%) rename {src/ffi => pyo3-ffi/src}/bytesobject.rs (97%) rename {src/ffi => pyo3-ffi/src}/ceval.rs (91%) rename {src/ffi => pyo3-ffi/src}/code.rs (100%) rename {src/ffi => pyo3-ffi/src}/codecs.rs (98%) rename {src/ffi => pyo3-ffi/src}/compile.rs (100%) rename {src/ffi => pyo3-ffi/src}/complexobject.rs (98%) rename {src/ffi => pyo3-ffi/src}/context.rs (96%) rename {src/ffi => pyo3-ffi/src}/cpython/abstract_.rs (98%) rename {src/ffi => pyo3-ffi/src}/cpython/bytesobject.rs (64%) rename {src/ffi => pyo3-ffi/src}/cpython/ceval.rs (55%) rename {src/ffi => pyo3-ffi/src}/cpython/code.rs (97%) rename {src/ffi => pyo3-ffi/src}/cpython/compile.rs (95%) rename {src/ffi => pyo3-ffi/src}/cpython/dictobject.rs (92%) rename {src/ffi => pyo3-ffi/src}/cpython/frameobject.rs (96%) rename {src/ffi => pyo3-ffi/src}/cpython/import.rs (97%) rename {src/ffi => pyo3-ffi/src}/cpython/initconfig.rs (99%) rename {src/ffi => pyo3-ffi/src}/cpython/listobject.rs (92%) rename {src/ffi => pyo3-ffi/src}/cpython/mod.rs (100%) rename {src/ffi => pyo3-ffi/src}/cpython/object.rs (95%) rename {src/ffi => pyo3-ffi/src}/cpython/pydebug.rs (100%) rename {src/ffi => pyo3-ffi/src}/cpython/pylifecycle.rs (95%) rename {src/ffi => pyo3-ffi/src}/cpython/pymem.rs (100%) rename {src/ffi => pyo3-ffi/src}/cpython/pystate.rs (93%) rename {src/ffi => pyo3-ffi/src}/cpython/pythonrun.rs (98%) rename {src/ffi => pyo3-ffi/src}/cpython/tupleobject.rs (93%) rename {src/ffi => pyo3-ffi/src}/cpython/unicodeobject.rs (73%) rename {src/ffi => pyo3-ffi/src}/datetime.rs (72%) rename {src/ffi => pyo3-ffi/src}/descrobject.rs (95%) rename {src/ffi => pyo3-ffi/src}/dictobject.rs (98%) rename {src/ffi => pyo3-ffi/src}/enumobject.rs (80%) rename {src/ffi => pyo3-ffi/src}/eval.rs (95%) rename {src/ffi => pyo3-ffi/src}/fileobject.rs (97%) rename {src/ffi => pyo3-ffi/src}/fileutils.rs (87%) rename {src/ffi => pyo3-ffi/src}/floatobject.rs (98%) rename {src/ffi => pyo3-ffi/src}/funcobject.rs (96%) rename {src/ffi => pyo3-ffi/src}/genobject.rs (95%) rename {src/ffi => pyo3-ffi/src}/import.rs (98%) rename {src/ffi => pyo3-ffi/src}/intrcheck.rs (100%) rename {src/ffi => pyo3-ffi/src}/iterobject.rs (96%) create mode 100644 pyo3-ffi/src/lib.rs rename {src/ffi => pyo3-ffi/src}/listobject.rs (97%) rename {src/ffi => pyo3-ffi/src}/longobject.rs (98%) rename {src/ffi => pyo3-ffi/src}/marshal.rs (100%) rename {src/ffi => pyo3-ffi/src}/memoryobject.rs (95%) rename {src/ffi => pyo3-ffi/src}/methodobject.rs (95%) rename {src/ffi => pyo3-ffi/src}/modsupport.rs (97%) rename {src/ffi => pyo3-ffi/src}/moduleobject.rs (96%) rename {src/ffi => pyo3-ffi/src}/object.rs (99%) rename {src/ffi => pyo3-ffi/src}/objimpl.rs (98%) rename {src/ffi => pyo3-ffi/src}/osmodule.rs (69%) rename {src/ffi => pyo3-ffi/src}/pyarena.rs (100%) rename {src/ffi => pyo3-ffi/src}/pycapsule.rs (98%) rename {src/ffi => pyo3-ffi/src}/pyerrors.rs (99%) rename {src/ffi => pyo3-ffi/src}/pyframe.rs (87%) rename {src/ffi => pyo3-ffi/src}/pyhash.rs (96%) rename {src/ffi => pyo3-ffi/src}/pylifecycle.rs (97%) rename {src/ffi => pyo3-ffi/src}/pymem.rs (100%) rename {src/ffi => pyo3-ffi/src}/pyport.rs (100%) rename {src/ffi => pyo3-ffi/src}/pystate.rs (97%) rename {src/ffi => pyo3-ffi/src}/pystrtod.rs (97%) rename {src/ffi => pyo3-ffi/src}/pythonrun.rs (93%) rename {src/ffi => pyo3-ffi/src}/rangeobject.rs (93%) rename {src/ffi => pyo3-ffi/src}/setobject.rs (96%) rename {src/ffi => pyo3-ffi/src}/sliceobject.rs (97%) rename {src/ffi => pyo3-ffi/src}/structmember.rs (96%) rename {src/ffi => pyo3-ffi/src}/structseq.rs (88%) rename {src/ffi => pyo3-ffi/src}/sysmodule.rs (97%) rename {src/ffi => pyo3-ffi/src}/traceback.rs (86%) rename {src/ffi => pyo3-ffi/src}/tupleobject.rs (95%) rename {src/ffi => pyo3-ffi/src}/typeslots.rs (100%) rename {src/ffi => pyo3-ffi/src}/unicodeobject.rs (99%) rename {src/ffi => pyo3-ffi/src}/warnings.rs (92%) rename {src/ffi => pyo3-ffi/src}/weakrefobject.rs (98%) delete mode 100644 src/ffi/README.md create mode 100644 src/ffi/tests.rs diff --git a/Architecture.md b/Architecture.md index 8e393473..f46f6db5 100644 --- a/Architecture.md +++ b/Architecture.md @@ -19,7 +19,7 @@ Since implementing `PyClass` requires lots of boilerplate, we have a proc-macro To summarize, there are six main parts to the PyO3 codebase. 1. [Low-level bindings of Python/C API.](#1-low-level-bindings-of-python-capi) - - [`src/ffi`] + - [`pyo3-ffi`] and [`src/ffi`] 2. [Bindings to Python objects.](#2-bindings-to-python-objects) - [`src/instance.rs`] and [`src/types`] 3. [`PyClass` and related functionalities.](#3-pyclass-and-related-functionalities) @@ -34,7 +34,7 @@ To summarize, there are six main parts to the PyO3 codebase. ## 1. Low-level bindings of Python/C API -[`src/ffi`] contains wrappers of [Python/C API]. +[`pyo3-ffi`] contains wrappers of [Python/C API]. We aim to provide straight-forward Rust wrappers resembling the file structure of [`cpython/Include`](https://github.com/python/cpython/tree/v3.9.2/Include). @@ -43,7 +43,7 @@ However, we still lack some APIs and are continuously updating the module to mat the file contents upstream in CPython. The tracking issue is [#1289](https://github.com/PyO3/pyo3/issues/1289), and contribution is welcome. -In the [`src/ffi`] module, there is lots of conditional compilation such as `#[cfg(Py_LIMITED_API)]`, +In the [`pyo3-ffi`] crate, there is lots of conditional compilation such as `#[cfg(Py_LIMITED_API)]`, `#[cfg(Py_37)]`, and `#[cfg(PyPy)]`. `Py_LIMITED_API` corresponds to `#define Py_LIMITED_API` macro in Python/C API. With `Py_LIMITED_API`, we can build a Python-version-agnostic binary called an @@ -208,6 +208,7 @@ Some of the functionality of `pyo3-build-config`: [`pyo3-macros`]: https://github.com/PyO3/pyo3/tree/main/pyo3-macros [`pyo3-macros-backend`]: https://github.com/PyO3/pyo3/tree/main/pyo3-macros-backend [`pyo3-build-config`]: https://github.com/PyO3/pyo3/tree/main/pyo3-build-config +[`pyo3-ffi`]: https://github.com/PyO3/pyo3/tree/main/pyo3-ffi diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e10423b..522922e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) - Update `inventory` optional dependency to 0.2. [#2019](https://github.com/PyO3/pyo3/pull/2019) - Drop `paste` dependency. [#2081](https://github.com/PyO3/pyo3/pull/2081) +- The bindings found `pyo3::ffi` are now a re-export of the new `pyo3-ffi` crate. [#2126](https://github.com/PyO3/pyo3/pull/2126) ### Added @@ -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) - `__ipow__` now supports modulo argument on Python 3.8+. [#2083](https://github.com/PyO3/pyo3/pull/2083) - `pyo3-macros-backend` is now compiled with PyO3 cfgs to enable different magic method definitions based on version. [#2083](https://github.com/PyO3/pyo3/pull/2083) +- `_PyCFunctionFast` now correctly reflects the signature defined in the [Python docs](https://docs.python.org/3/c-api/structures.html#c._PyCFunctionFast). [#2126](https://github.com/PyO3/pyo3/pull/2126) +- `PyDateTimeAPI` and `PyDateTime_TimeZone_UTC` are are now unsafe functions instead of statics. [#2126](https://github.com/PyO3/pyo3/pull/2126) +- `PyDateTimeAPI` does not implicitly call `PyDateTime_IMPORT` anymore to reflect the original Python API more closely. Before the first call to `PyDateTime_IMPORT` a null pointer is returned. Therefore before calling any of the following FFI functions `PyDateTime_IMPORT` must be called to avoid undefined behaviour: [#2126](https://github.com/PyO3/pyo3/pull/2126) + - `PyDateTime_TimeZone_UTC` + - `PyDate_Check` + - `PyDate_CheckExact` + - `PyDateTime_Check` + - `PyDateTime_CheckExact` + - `PyTime_Check` + - `PyTime_CheckExact` + - `PyDelta_Check` + - `PyDelta_CheckExact` + - `PyTZInfo_Check` + - `PyTZInfo_CheckExact` + - `PyDateTime_FromTimestamp` + - `PyDate_FromTimestamp` ### Removed diff --git a/Cargo.toml b/Cargo.toml index fc69989c..7552ea88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,9 @@ cfg-if = "1.0" libc = "0.2.62" parking_lot = "0.11.0" +# ffi bindings to the python interpreter, split into a seperate crate so they can be used independently +pyo3-ffi = { path = "pyo3-ffi", version = "=0.15.1" } + # support crates for macros feature pyo3-macros = { path = "pyo3-macros", version = "=0.15.1", optional = true } indoc = { version = "1.0.3", optional = true } @@ -60,16 +63,16 @@ multiple-pymethods = ["inventory", "pyo3-macros/multiple-pymethods"] # Use this feature when building an extension module. # It tells the linker to keep the python symbols unresolved, # so that the module can also be used with statically linked python interpreters. -extension-module = [] +extension-module = ["pyo3-ffi/extension-module"] # Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more. -abi3 = ["pyo3-build-config/abi3"] +abi3 = ["pyo3-build-config/abi3", "pyo3-ffi/abi3"] # With abi3, we can manually set the minimum Python version. -abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37"] -abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38"] -abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39"] -abi3-py310 = ["abi3", "pyo3-build-config/abi3-py310"] +abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37", "pyo3-ffi/abi3-py37"] +abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38", "pyo3-ffi/abi3-py38"] +abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39", "pyo3-ffi/abi3-py39"] +abi3-py310 = ["abi3", "pyo3-build-config/abi3-py310", "pyo3-ffi/abi3-py310"] # Changes `Python::with_gil` and `Python::acquire_gil` to automatically initialize the # Python interpreter if needed. @@ -126,6 +129,7 @@ harness = false [workspace] members = [ + "pyo3-ffi", "pyo3-macros", "pyo3-macros-backend", "pytests/pyo3-benchmarks", diff --git a/build.rs b/build.rs index e0f2845b..cfaf06f6 100644 --- a/build.rs +++ b/build.rs @@ -1,48 +1,7 @@ use std::{env, process::Command}; -use pyo3_build_config::{ - bail, ensure, - pyo3_build_script_impl::{ - cargo_env_var, env_var, errors::Result, resolve_interpreter_config, InterpreterConfig, - PythonVersion, - }, -}; - -/// Minimum Python version PyO3 supports. -const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 6 }; - -fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { - ensure!( - interpreter_config.version >= MINIMUM_SUPPORTED_VERSION, - "the configured Python interpreter version ({}) is lower than PyO3's minimum supported version ({})", - interpreter_config.version, - MINIMUM_SUPPORTED_VERSION, - ); - - Ok(()) -} - -fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result<()> { - if let Some(pointer_width) = interpreter_config.pointer_width { - // Try to check whether the target architecture matches the python library - let rust_target = match cargo_env_var("CARGO_CFG_TARGET_POINTER_WIDTH") - .unwrap() - .as_str() - { - "64" => 64, - "32" => 32, - x => bail!("unexpected Rust target pointer width: {}", x), - }; - - ensure!( - rust_target == pointer_width, - "your Rust target architecture ({}-bit) does not match your python interpreter ({}-bit)", - rust_target, - pointer_width - ); - } - Ok(()) -} +use pyo3_build_config::pyo3_build_script_impl::{cargo_env_var, errors::Result}; +use pyo3_build_config::{bail, InterpreterConfig}; fn ensure_auto_initialize_ok(interpreter_config: &InterpreterConfig) -> Result<()> { if cargo_env_var("CARGO_FEATURE_AUTO_INITIALIZE").is_some() { @@ -84,36 +43,6 @@ fn rustc_minor_version() -> Option { pieces.next()?.parse().ok() } -fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> { - let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap(); - let is_extension_module = cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some(); - if target_os == "windows" || target_os == "android" || !is_extension_module { - // windows and android - always link - // other systems - only link if not extension module - println!( - "cargo:rustc-link-lib={link_model}{alias}{lib_name}", - link_model = if interpreter_config.shared { - "" - } else { - "static=" - }, - alias = if target_os == "windows" { - "pythonXY:" - } else { - "" - }, - lib_name = interpreter_config.lib_name.as_ref().ok_or( - "attempted to link to Python shared library but config does not contain lib_name" - )?, - ); - if let Some(lib_dir) = &interpreter_config.lib_dir { - println!("cargo:rustc-link-search=native={}", lib_dir); - } - } - - Ok(()) -} - /// Prepares the PyO3 crate for compilation. /// /// This loads the config from pyo3-build-config and then makes some additional checks to improve UX @@ -122,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 /// version to enable features which aren't supported on MSRV. fn configure_pyo3() -> Result<()> { - let interpreter_config = resolve_interpreter_config()?; - - if env_var("PYO3_PRINT_CONFIG").map_or(false, |os_str| os_str == "1") { - print_config_and_exit(&interpreter_config); - } - - ensure_python_version(&interpreter_config)?; - ensure_target_pointer_width(&interpreter_config)?; - ensure_auto_initialize_ok(&interpreter_config)?; - - if !interpreter_config.suppress_build_script_link_lines { - emit_link_config(&interpreter_config)?; - } + let interpreter_config = pyo3_build_config::get(); interpreter_config.emit_pyo3_cfgs(); let rustc_minor_version = rustc_minor_version().unwrap_or(0); + ensure_auto_initialize_ok(interpreter_config)?; // Enable use of const generics on Rust 1.51 and greater if rustc_minor_version >= 51 { @@ -150,22 +68,9 @@ fn configure_pyo3() -> Result<()> { 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(()) } -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()); diff --git a/pyo3-ffi/Cargo.toml b/pyo3-ffi/Cargo.toml new file mode 100644 index 00000000..d9d15f9d --- /dev/null +++ b/pyo3-ffi/Cargo.toml @@ -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 "] +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"] } + + diff --git a/src/ffi/LICENSE b/pyo3-ffi/LICENSE similarity index 100% rename from src/ffi/LICENSE rename to pyo3-ffi/LICENSE diff --git a/pyo3-ffi/README.md b/pyo3-ffi/README.md new file mode 100644 index 00000000..a7a36133 --- /dev/null +++ b/pyo3-ffi/README.md @@ -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" diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs new file mode 100644 index 00000000..e72cfb97 --- /dev/null +++ b/pyo3-ffi/build.rs @@ -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) + } +} diff --git a/src/ffi/abstract_.rs b/pyo3-ffi/src/abstract_.rs similarity index 98% rename from src/ffi/abstract_.rs rename to pyo3-ffi/src/abstract_.rs index 07e02da4..172396aa 100644 --- a/src/ffi/abstract_.rs +++ b/pyo3-ffi/src/abstract_.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; use std::os::raw::{c_char, c_int}; use std::ptr; @@ -96,10 +96,9 @@ extern "C" { #[cfg(not(any(Py_LIMITED_API, PyPy)))] #[inline] pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int { - (match (*crate::ffi::Py_TYPE(o)).tp_iternext { + (match (*crate::Py_TYPE(o)).tp_iternext { Some(tp_iternext) => { - tp_iternext as *const std::os::raw::c_void - != crate::ffi::_PyObject_NextNotImplemented as _ + tp_iternext as *const std::os::raw::c_void != crate::_PyObject_NextNotImplemented as _ } None => false, }) as c_int diff --git a/src/ffi/bltinmodule.rs b/pyo3-ffi/src/bltinmodule.rs similarity index 84% rename from src/ffi/bltinmodule.rs rename to pyo3-ffi/src/bltinmodule.rs index f7069178..cd5be043 100644 --- a/src/ffi/bltinmodule.rs +++ b/pyo3-ffi/src/bltinmodule.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::PyTypeObject; +use crate::object::PyTypeObject; #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { diff --git a/src/ffi/boolobject.rs b/pyo3-ffi/src/boolobject.rs similarity index 94% rename from src/ffi/boolobject.rs rename to pyo3-ffi/src/boolobject.rs index 92d77d01..2b72db3f 100644 --- a/src/ffi/boolobject.rs +++ b/pyo3-ffi/src/boolobject.rs @@ -1,5 +1,5 @@ -use crate::ffi::longobject::PyLongObject; -use crate::ffi::object::*; +use crate::longobject::PyLongObject; +use crate::object::*; use std::os::raw::{c_int, c_long}; #[cfg_attr(windows, link(name = "pythonXY"))] diff --git a/src/ffi/bytearrayobject.rs b/pyo3-ffi/src/bytearrayobject.rs similarity index 95% rename from src/ffi/bytearrayobject.rs rename to pyo3-ffi/src/bytearrayobject.rs index e7131a17..d8df2d6a 100644 --- a/src/ffi/bytearrayobject.rs +++ b/pyo3-ffi/src/bytearrayobject.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; use std::os::raw::{c_char, c_int}; #[cfg_attr(windows, link(name = "pythonXY"))] diff --git a/src/ffi/bytesobject.rs b/pyo3-ffi/src/bytesobject.rs similarity index 97% rename from src/ffi/bytesobject.rs rename to pyo3-ffi/src/bytesobject.rs index 1e52a47b..058f402c 100644 --- a/src/ffi/bytesobject.rs +++ b/pyo3-ffi/src/bytesobject.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; use std::os::raw::{c_char, c_int}; #[cfg_attr(windows, link(name = "pythonXY"))] diff --git a/src/ffi/ceval.rs b/pyo3-ffi/src/ceval.rs similarity index 91% rename from src/ffi/ceval.rs rename to pyo3-ffi/src/ceval.rs index 4031daa7..a45b3059 100644 --- a/src/ffi/ceval.rs +++ b/pyo3-ffi/src/ceval.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::PyObject; -use crate::ffi::pystate::PyThreadState; +use crate::object::PyObject; +use crate::pystate::PyThreadState; use std::os::raw::{c_char, c_int, c_void}; extern "C" { @@ -37,7 +37,7 @@ extern "C" { pub fn PyEval_GetGlobals() -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyEval_GetLocals")] pub fn PyEval_GetLocals() -> *mut PyObject; - pub fn PyEval_GetFrame() -> *mut crate::ffi::PyFrameObject; + pub fn PyEval_GetFrame() -> *mut crate::PyFrameObject; #[cfg_attr(PyPy, link_name = "PyPy_AddPendingCall")] pub fn Py_AddPendingCall( func: Option c_int>, @@ -59,8 +59,8 @@ extern "C" { pub fn PyEval_GetFuncName(arg1: *mut PyObject) -> *const c_char; pub fn PyEval_GetFuncDesc(arg1: *mut PyObject) -> *const c_char; pub fn PyEval_GetCallStats(arg1: *mut PyObject) -> *mut PyObject; - pub fn PyEval_EvalFrame(arg1: *mut crate::ffi::PyFrameObject) -> *mut PyObject; - pub fn PyEval_EvalFrameEx(f: *mut crate::ffi::PyFrameObject, exc: c_int) -> *mut PyObject; + pub fn PyEval_EvalFrame(arg1: *mut crate::PyFrameObject) -> *mut PyObject; + pub fn PyEval_EvalFrameEx(f: *mut crate::PyFrameObject, exc: c_int) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyEval_SaveThread")] pub fn PyEval_SaveThread() -> *mut PyThreadState; #[cfg_attr(PyPy, link_name = "PyPyEval_RestoreThread")] diff --git a/src/ffi/code.rs b/pyo3-ffi/src/code.rs similarity index 100% rename from src/ffi/code.rs rename to pyo3-ffi/src/code.rs diff --git a/src/ffi/codecs.rs b/pyo3-ffi/src/codecs.rs similarity index 98% rename from src/ffi/codecs.rs rename to pyo3-ffi/src/codecs.rs index 6c23e296..2fd214cb 100644 --- a/src/ffi/codecs.rs +++ b/pyo3-ffi/src/codecs.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::PyObject; +use crate::object::PyObject; use std::os::raw::{c_char, c_int}; extern "C" { diff --git a/src/ffi/compile.rs b/pyo3-ffi/src/compile.rs similarity index 100% rename from src/ffi/compile.rs rename to pyo3-ffi/src/compile.rs diff --git a/src/ffi/complexobject.rs b/pyo3-ffi/src/complexobject.rs similarity index 98% rename from src/ffi/complexobject.rs rename to pyo3-ffi/src/complexobject.rs index beecb421..829adf82 100644 --- a/src/ffi/complexobject.rs +++ b/pyo3-ffi/src/complexobject.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::*; +use crate::object::*; use std::os::raw::{c_double, c_int}; #[repr(C)] diff --git a/src/ffi/context.rs b/pyo3-ffi/src/context.rs similarity index 96% rename from src/ffi/context.rs rename to pyo3-ffi/src/context.rs index f3872a69..d720cbe0 100644 --- a/src/ffi/context.rs +++ b/pyo3-ffi/src/context.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::{PyObject, PyTypeObject, Py_TYPE}; +use crate::object::{PyObject, PyTypeObject, Py_TYPE}; use std::os::raw::{c_char, c_int}; extern "C" { diff --git a/src/ffi/cpython/abstract_.rs b/pyo3-ffi/src/cpython/abstract_.rs similarity index 98% rename from src/ffi/cpython/abstract_.rs rename to pyo3-ffi/src/cpython/abstract_.rs index b86ed1a7..a7383c2f 100644 --- a/src/ffi/cpython/abstract_.rs +++ b/pyo3-ffi/src/cpython/abstract_.rs @@ -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}; #[cfg(all(Py_3_8, not(PyPy)))] -use crate::ffi::{ +use crate::{ pyport::PY_SSIZE_T_MAX, vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET, PyTuple_Check, PyType_HasFeature, Py_TPFLAGS_HAVE_VECTORCALL, }; @@ -51,7 +51,7 @@ pub unsafe fn PyVectorcall_NARGS(n: size_t) -> Py_ssize_t { #[inline(always)] pub unsafe fn PyVectorcall_Function(callable: *mut PyObject) -> Option { assert!(!callable.is_null()); - let tp = crate::ffi::Py_TYPE(callable); + let tp = crate::Py_TYPE(callable); if PyType_HasFeature(tp, Py_TPFLAGS_HAVE_VECTORCALL) == 0 { return None; } @@ -218,7 +218,7 @@ extern "C" { #[cfg(not(any(Py_3_9, PyPy)))] #[inline] pub unsafe fn PyObject_CheckBuffer(o: *mut PyObject) -> c_int { - let tp_as_buffer = (*crate::ffi::Py_TYPE(o)).tp_as_buffer; + let tp_as_buffer = (*crate::Py_TYPE(o)).tp_as_buffer; (!tp_as_buffer.is_null() && (*tp_as_buffer).bf_getbuffer.is_some()) as c_int } diff --git a/src/ffi/cpython/bytesobject.rs b/pyo3-ffi/src/cpython/bytesobject.rs similarity index 64% rename from src/ffi/cpython/bytesobject.rs rename to pyo3-ffi/src/cpython/bytesobject.rs index 108dbb0c..9d02c32b 100644 --- a/src/ffi/cpython/bytesobject.rs +++ b/pyo3-ffi/src/cpython/bytesobject.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::PyObject; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::PyObject; +use crate::pyport::Py_ssize_t; use std::os::raw::c_int; extern "C" { diff --git a/src/ffi/cpython/ceval.rs b/pyo3-ffi/src/cpython/ceval.rs similarity index 55% rename from src/ffi/cpython/ceval.rs rename to pyo3-ffi/src/cpython/ceval.rs index b85c4d07..78678cd1 100644 --- a/src/ffi/cpython/ceval.rs +++ b/pyo3-ffi/src/cpython/ceval.rs @@ -1,12 +1,9 @@ -use crate::ffi::cpython::pystate::Py_tracefunc; -use crate::ffi::object::{freefunc, PyObject}; +use crate::cpython::pystate::Py_tracefunc; +use crate::object::{freefunc, PyObject}; use std::os::raw::c_int; extern "C" { - pub fn _PyEval_EvalFrameDefault( - arg1: *mut crate::ffi::PyFrameObject, - exc: c_int, - ) -> *mut PyObject; + pub fn _PyEval_EvalFrameDefault(arg1: *mut crate::PyFrameObject, exc: c_int) -> *mut PyObject; pub fn _PyEval_RequestCodeExtraIndex(func: freefunc) -> c_int; pub fn PyEval_SetProfile(trace_func: Option, arg1: *mut PyObject); pub fn PyEval_SetTrace(trace_func: Option, arg1: *mut PyObject); diff --git a/src/ffi/cpython/code.rs b/pyo3-ffi/src/cpython/code.rs similarity index 97% rename from src/ffi/cpython/code.rs rename to pyo3-ffi/src/cpython/code.rs index 7ebceb9f..93657855 100644 --- a/src/ffi/cpython/code.rs +++ b/pyo3-ffi/src/cpython/code.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; use std::os::raw::{c_char, c_int, c_uchar, c_void}; // skipped _Py_CODEUNIT @@ -90,7 +90,7 @@ pub unsafe fn PyCode_Check(op: *mut PyObject) -> c_int { #[inline] #[cfg(not(PyPy))] pub unsafe fn PyCode_GetNumFree(op: *mut PyCodeObject) -> Py_ssize_t { - crate::ffi::PyTuple_GET_SIZE((*op).co_freevars) + crate::PyTuple_GET_SIZE((*op).co_freevars) } extern "C" { diff --git a/src/ffi/cpython/compile.rs b/pyo3-ffi/src/cpython/compile.rs similarity index 95% rename from src/ffi/cpython/compile.rs rename to pyo3-ffi/src/cpython/compile.rs index 4308730e..aa459d33 100644 --- a/src/ffi/cpython/compile.rs +++ b/pyo3-ffi/src/cpython/compile.rs @@ -1,11 +1,11 @@ #[cfg(not(Py_3_10))] -use crate::ffi::object::PyObject; +use crate::object::PyObject; #[cfg(not(Py_3_10))] -use crate::ffi::pyarena::*; +use crate::pyarena::*; #[cfg(not(Py_3_10))] -use crate::ffi::pythonrun::*; +use crate::pythonrun::*; #[cfg(not(Py_3_10))] -use crate::ffi::PyCodeObject; +use crate::PyCodeObject; #[cfg(not(Py_3_10))] use std::os::raw::c_char; use std::os::raw::c_int; diff --git a/src/ffi/cpython/dictobject.rs b/pyo3-ffi/src/cpython/dictobject.rs similarity index 92% rename from src/ffi/cpython/dictobject.rs rename to pyo3-ffi/src/cpython/dictobject.rs index 3e58e7b9..480cecc7 100644 --- a/src/ffi/cpython/dictobject.rs +++ b/pyo3-ffi/src/cpython/dictobject.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; use std::os::raw::c_int; opaque_struct!(PyDictKeysObject); @@ -24,7 +24,7 @@ extern "C" { mp: *mut PyObject, key: *mut PyObject, item: *mut PyObject, - hash: crate::ffi::Py_hash_t, + hash: crate::Py_hash_t, ) -> c_int; // skipped _PyDict_DelItem_KnownHash // skipped _PyDict_DelItemIf @@ -34,7 +34,7 @@ extern "C" { pos: *mut Py_ssize_t, key: *mut *mut PyObject, value: *mut *mut PyObject, - hash: *mut crate::ffi::Py_hash_t, + hash: *mut crate::Py_hash_t, ) -> c_int; // skipped PyDict_GET_SIZE // skipped _PyDict_Contains_KnownHash diff --git a/src/ffi/cpython/frameobject.rs b/pyo3-ffi/src/cpython/frameobject.rs similarity index 96% rename from src/ffi/cpython/frameobject.rs rename to pyo3-ffi/src/cpython/frameobject.rs index 2f7d2b1d..53d2d494 100644 --- a/src/ffi/cpython/frameobject.rs +++ b/pyo3-ffi/src/cpython/frameobject.rs @@ -1,6 +1,6 @@ -use crate::ffi::cpython::code::{PyCodeObject, CO_MAXBLOCKS}; -use crate::ffi::object::*; -use crate::ffi::pystate::PyThreadState; +use crate::cpython::code::{PyCodeObject, CO_MAXBLOCKS}; +use crate::object::*; +use crate::pystate::PyThreadState; use std::os::raw::{c_char, c_int}; // skipped _framestate diff --git a/src/ffi/cpython/import.rs b/pyo3-ffi/src/cpython/import.rs similarity index 97% rename from src/ffi/cpython/import.rs rename to pyo3-ffi/src/cpython/import.rs index f342c0e3..d85eb5fa 100644 --- a/src/ffi/cpython/import.rs +++ b/pyo3-ffi/src/cpython/import.rs @@ -1,4 +1,4 @@ -use crate::ffi::{PyInterpreterState, PyObject}; +use crate::{PyInterpreterState, PyObject}; use std::os::raw::{c_char, c_int, c_uchar}; // skipped PyInit__imp diff --git a/src/ffi/cpython/initconfig.rs b/pyo3-ffi/src/cpython/initconfig.rs similarity index 99% rename from src/ffi/cpython/initconfig.rs rename to pyo3-ffi/src/cpython/initconfig.rs index 95d28b02..552a3664 100644 --- a/src/ffi/cpython/initconfig.rs +++ b/pyo3-ffi/src/cpython/initconfig.rs @@ -1,6 +1,6 @@ /* --- PyStatus ----------------------------------------------- */ -use crate::ffi::Py_ssize_t; +use crate::Py_ssize_t; use libc::wchar_t; use std::os::raw::{c_char, c_int, c_ulong}; diff --git a/src/ffi/cpython/listobject.rs b/pyo3-ffi/src/cpython/listobject.rs similarity index 92% rename from src/ffi/cpython/listobject.rs rename to pyo3-ffi/src/cpython/listobject.rs index 04894078..13b82ff1 100644 --- a/src/ffi/cpython/listobject.rs +++ b/pyo3-ffi/src/cpython/listobject.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; #[repr(C)] #[derive(Copy, Clone)] diff --git a/src/ffi/cpython/mod.rs b/pyo3-ffi/src/cpython/mod.rs similarity index 100% rename from src/ffi/cpython/mod.rs rename to pyo3-ffi/src/cpython/mod.rs diff --git a/src/ffi/cpython/object.rs b/pyo3-ffi/src/cpython/object.rs similarity index 95% rename from src/ffi/cpython/object.rs rename to pyo3-ffi/src/cpython/object.rs index 31a7c3d5..08c79977 100644 --- a/src/ffi/cpython/object.rs +++ b/pyo3-ffi/src/cpython/object.rs @@ -1,4 +1,4 @@ -use crate::ffi::PyObject; +use crate::PyObject; use std::os::raw::c_int; // skipped _Py_NewReference @@ -12,7 +12,7 @@ use std::os::raw::c_int; // skipped _Py_IDENTIFIER mod bufferinfo { - use crate::ffi::Py_ssize_t; + use crate::Py_ssize_t; use std::os::raw::{c_char, c_int, c_void}; use std::ptr; @@ -24,7 +24,7 @@ mod bufferinfo { pub struct Py_buffer { pub buf: *mut c_void, /// Owned reference - pub obj: *mut crate::ffi::PyObject, + pub obj: *mut crate::PyObject, pub len: Py_ssize_t, pub itemsize: Py_ssize_t, pub readonly: c_int, @@ -67,12 +67,12 @@ mod bufferinfo { } pub type getbufferproc = unsafe extern "C" fn( - arg1: *mut crate::ffi::PyObject, + arg1: *mut crate::PyObject, arg2: *mut Py_buffer, arg3: c_int, ) -> c_int; pub type releasebufferproc = - unsafe extern "C" fn(arg1: *mut crate::ffi::PyObject, arg2: *mut Py_buffer); + unsafe extern "C" fn(arg1: *mut crate::PyObject, arg2: *mut Py_buffer); /// Maximum number of dimensions pub const PyBUF_MAX_NDIM: c_int = 64; @@ -117,8 +117,8 @@ pub type vectorcallfunc = unsafe extern "C" fn( ) -> *mut PyObject; mod typeobject { - use crate::ffi::{self, object}; - use crate::ffi::{PyObject, Py_ssize_t}; + use crate::object; + use crate::{PyObject, Py_ssize_t}; use std::mem; use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void}; @@ -248,9 +248,9 @@ mod typeobject { pub tp_weaklistoffset: Py_ssize_t, pub tp_iter: Option, pub tp_iternext: Option, - pub tp_methods: *mut ffi::methodobject::PyMethodDef, - pub tp_members: *mut ffi::structmember::PyMemberDef, - pub tp_getset: *mut ffi::descrobject::PyGetSetDef, + pub tp_methods: *mut crate::methodobject::PyMethodDef, + pub tp_members: *mut crate::structmember::PyMemberDef, + pub tp_getset: *mut crate::descrobject::PyGetSetDef, pub tp_base: *mut PyTypeObject, pub tp_dict: *mut object::PyObject, pub tp_descr_get: Option, @@ -310,10 +310,10 @@ mod typeobject { #[inline] pub unsafe fn PyHeapType_GET_MEMBERS( etype: *mut PyHeapTypeObject, - ) -> *mut ffi::structmember::PyMemberDef { + ) -> *mut crate::structmember::PyMemberDef { let py_type = object::Py_TYPE(etype as *mut object::PyObject); let ptr = etype.offset((*py_type).tp_basicsize); - ptr as *mut ffi::structmember::PyMemberDef + ptr as *mut crate::structmember::PyMemberDef } } diff --git a/src/ffi/cpython/pydebug.rs b/pyo3-ffi/src/cpython/pydebug.rs similarity index 100% rename from src/ffi/cpython/pydebug.rs rename to pyo3-ffi/src/cpython/pydebug.rs diff --git a/src/ffi/cpython/pylifecycle.rs b/pyo3-ffi/src/cpython/pylifecycle.rs similarity index 95% rename from src/ffi/cpython/pylifecycle.rs rename to pyo3-ffi/src/cpython/pylifecycle.rs index b05cbe8c..3f1f16e2 100644 --- a/src/ffi/cpython/pylifecycle.rs +++ b/pyo3-ffi/src/cpython/pylifecycle.rs @@ -1,4 +1,4 @@ -use crate::ffi::{PyConfig, PyPreConfig, PyStatus, Py_ssize_t}; +use crate::{PyConfig, PyPreConfig, PyStatus, Py_ssize_t}; use libc::wchar_t; use std::os::raw::{c_char, c_int}; diff --git a/src/ffi/cpython/pymem.rs b/pyo3-ffi/src/cpython/pymem.rs similarity index 100% rename from src/ffi/cpython/pymem.rs rename to pyo3-ffi/src/cpython/pymem.rs diff --git a/src/ffi/cpython/pystate.rs b/pyo3-ffi/src/cpython/pystate.rs similarity index 93% rename from src/ffi/cpython/pystate.rs rename to pyo3-ffi/src/cpython/pystate.rs index 3f545450..67fa42d3 100644 --- a/src/ffi/cpython/pystate.rs +++ b/pyo3-ffi/src/cpython/pystate.rs @@ -1,6 +1,6 @@ #[cfg(not(PyPy))] -use crate::ffi::PyThreadState; -use crate::ffi::{PyFrameObject, PyInterpreterState, PyObject}; +use crate::PyThreadState; +use crate::{PyFrameObject, PyInterpreterState, PyObject}; use std::os::raw::c_int; // skipped _PyInterpreterState_RequiresIDRef @@ -60,10 +60,10 @@ extern "C" { #[cfg(Py_3_9)] pub type _PyFrameEvalFunction = extern "C" fn( - *mut crate::ffi::PyThreadState, - *mut crate::ffi::PyFrameObject, + *mut crate::PyThreadState, + *mut crate::PyFrameObject, c_int, -) -> *mut crate::ffi::object::PyObject; +) -> *mut crate::object::PyObject; #[cfg(Py_3_9)] extern "C" { diff --git a/src/ffi/cpython/pythonrun.rs b/pyo3-ffi/src/cpython/pythonrun.rs similarity index 98% rename from src/ffi/cpython/pythonrun.rs rename to pyo3-ffi/src/cpython/pythonrun.rs index 8c3a41f2..ba86fe33 100644 --- a/src/ffi/cpython/pythonrun.rs +++ b/pyo3-ffi/src/cpython/pythonrun.rs @@ -1,9 +1,9 @@ -use crate::ffi::object::*; +use crate::object::*; #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))] -use crate::ffi::pyarena::PyArena; -use crate::ffi::PyCompilerFlags; +use crate::pyarena::PyArena; +use crate::PyCompilerFlags; #[cfg(not(Py_3_10))] -use crate::ffi::{_mod, _node}; +use crate::{_mod, _node}; use libc::FILE; use std::os::raw::{c_char, c_int}; diff --git a/src/ffi/cpython/tupleobject.rs b/pyo3-ffi/src/cpython/tupleobject.rs similarity index 93% rename from src/ffi/cpython/tupleobject.rs rename to pyo3-ffi/src/cpython/tupleobject.rs index 16993a41..77baf95c 100644 --- a/src/ffi/cpython/tupleobject.rs +++ b/pyo3-ffi/src/cpython/tupleobject.rs @@ -1,6 +1,6 @@ -use crate::ffi::object::*; +use crate::object::*; #[cfg(not(PyPy))] -use crate::ffi::pyport::Py_ssize_t; +use crate::pyport::Py_ssize_t; #[repr(C)] pub struct PyTupleObject { diff --git a/src/ffi/cpython/unicodeobject.rs b/pyo3-ffi/src/cpython/unicodeobject.rs similarity index 73% rename from src/ffi/cpython/unicodeobject.rs rename to pyo3-ffi/src/cpython/unicodeobject.rs index 00b832fc..0fa355f9 100644 --- a/src/ffi/cpython/unicodeobject.rs +++ b/pyo3-ffi/src/cpython/unicodeobject.rs @@ -1,4 +1,4 @@ -use crate::ffi::{PyObject, Py_UCS1, Py_UCS2, Py_UCS4, Py_UNICODE, Py_hash_t, Py_ssize_t}; +use crate::{PyObject, Py_UCS1, Py_UCS2, Py_UCS4, Py_UNICODE, Py_hash_t, Py_ssize_t}; use libc::wchar_t; use std::os::raw::{c_char, c_int, c_uint, c_void}; @@ -119,7 +119,7 @@ pub const SSTATE_INTERNED_IMMORTAL: c_uint = 2; #[inline] #[cfg(target_endian = "little")] pub unsafe fn PyUnicode_IS_ASCII(op: *mut PyObject) -> c_uint { - debug_assert!(crate::ffi::PyUnicode_Check(op) != 0); + debug_assert!(crate::PyUnicode_Check(op) != 0); debug_assert!(PyUnicode_IS_READY(op) != 0); (*(op as *mut PyASCIIObject)).ascii() @@ -170,7 +170,7 @@ pub unsafe fn PyUnicode_4BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS4 { #[inline] #[cfg(target_endian = "little")] pub unsafe fn PyUnicode_KIND(op: *mut PyObject) -> c_uint { - debug_assert!(crate::ffi::PyUnicode_Check(op) != 0); + debug_assert!(crate::PyUnicode_Check(op) != 0); debug_assert!(PyUnicode_IS_READY(op) != 0); (*(op as *mut PyASCIIObject)).kind() @@ -197,7 +197,7 @@ pub unsafe fn _PyUnicode_NONCOMPACT_DATA(op: *mut PyObject) -> *mut c_void { #[inline] #[cfg(target_endian = "little")] pub unsafe fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void { - debug_assert!(crate::ffi::PyUnicode_Check(op) != 0); + debug_assert!(crate::PyUnicode_Check(op) != 0); if PyUnicode_IS_COMPACT(op) != 0 { _PyUnicode_COMPACT_DATA(op) @@ -213,7 +213,7 @@ pub unsafe fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void { #[inline] #[cfg(target_endian = "little")] pub unsafe fn PyUnicode_GET_LENGTH(op: *mut PyObject) -> Py_ssize_t { - debug_assert!(crate::ffi::PyUnicode_Check(op) != 0); + debug_assert!(crate::PyUnicode_Check(op) != 0); debug_assert!(PyUnicode_IS_READY(op) != 0); (*(op as *mut PyASCIIObject)).length @@ -230,7 +230,7 @@ pub unsafe fn PyUnicode_IS_READY(op: *mut PyObject) -> c_uint { #[inline] #[cfg(target_endian = "little")] pub unsafe fn PyUnicode_READY(op: *mut PyObject) -> c_int { - debug_assert!(crate::ffi::PyUnicode_Check(op) != 0); + debug_assert!(crate::PyUnicode_Check(op) != 0); if PyUnicode_IS_READY(op) != 0 { 0 @@ -480,130 +480,3 @@ extern "C" { // skipped _PyUnicode_FromId // skipped _PyUnicode_EQ // skipped _PyUnicode_ScanIdentifier - -#[cfg(test)] -#[cfg(target_endian = "little")] -mod tests { - use super::*; - use crate::types::PyString; - use crate::{AsPyPointer, Python}; - - #[test] - fn ascii_object_bitfield() { - let ob_base: PyObject = unsafe { std::mem::zeroed() }; - - let mut o = PyASCIIObject { - ob_base, - length: 0, - hash: 0, - state: 0, - wstr: std::ptr::null_mut() as *mut wchar_t, - }; - - unsafe { - assert_eq!(o.interned(), 0); - assert_eq!(o.kind(), 0); - assert_eq!(o.compact(), 0); - assert_eq!(o.ascii(), 0); - assert_eq!(o.ready(), 0); - - for i in 0..4 { - o.state = i; - assert_eq!(o.interned(), i); - } - - for i in 0..8 { - o.state = i << 2; - assert_eq!(o.kind(), i); - } - - o.state = 1 << 5; - assert_eq!(o.compact(), 1); - - o.state = 1 << 6; - assert_eq!(o.ascii(), 1); - - o.state = 1 << 7; - assert_eq!(o.ready(), 1); - } - } - - #[test] - #[cfg_attr(Py_3_10, allow(deprecated))] - fn ascii() { - Python::with_gil(|py| { - // This test relies on implementation details of PyString. - let s = PyString::new(py, "hello, world"); - let ptr = s.as_ptr(); - - unsafe { - let ascii_ptr = ptr as *mut PyASCIIObject; - let ascii = ascii_ptr.as_ref().unwrap(); - - assert_eq!(ascii.interned(), 0); - assert_eq!(ascii.kind(), PyUnicode_1BYTE_KIND); - assert_eq!(ascii.compact(), 1); - assert_eq!(ascii.ascii(), 1); - assert_eq!(ascii.ready(), 1); - - assert_eq!(PyUnicode_IS_ASCII(ptr), 1); - assert_eq!(PyUnicode_IS_COMPACT(ptr), 1); - assert_eq!(PyUnicode_IS_COMPACT_ASCII(ptr), 1); - - assert!(!PyUnicode_1BYTE_DATA(ptr).is_null()); - // 2 and 4 byte macros return nonsense for this string instance. - assert_eq!(PyUnicode_KIND(ptr), PyUnicode_1BYTE_KIND); - - assert!(!_PyUnicode_COMPACT_DATA(ptr).is_null()); - // _PyUnicode_NONCOMPACT_DATA isn't valid for compact strings. - assert!(!PyUnicode_DATA(ptr).is_null()); - - assert_eq!(PyUnicode_GET_LENGTH(ptr), s.len().unwrap() as _); - assert_eq!(PyUnicode_IS_READY(ptr), 1); - - // This has potential to mutate object. But it should be a no-op since - // we're already ready. - assert_eq!(PyUnicode_READY(ptr), 0); - } - }) - } - - #[test] - #[cfg_attr(Py_3_10, allow(deprecated))] - fn ucs4() { - Python::with_gil(|py| { - let s = "哈哈🐈"; - let py_string = PyString::new(py, s); - let ptr = py_string.as_ptr(); - - unsafe { - let ascii_ptr = ptr as *mut PyASCIIObject; - let ascii = ascii_ptr.as_ref().unwrap(); - - assert_eq!(ascii.interned(), 0); - assert_eq!(ascii.kind(), PyUnicode_4BYTE_KIND); - assert_eq!(ascii.compact(), 1); - assert_eq!(ascii.ascii(), 0); - assert_eq!(ascii.ready(), 1); - - assert_eq!(PyUnicode_IS_ASCII(ptr), 0); - assert_eq!(PyUnicode_IS_COMPACT(ptr), 1); - assert_eq!(PyUnicode_IS_COMPACT_ASCII(ptr), 0); - - assert!(!PyUnicode_4BYTE_DATA(ptr).is_null()); - assert_eq!(PyUnicode_KIND(ptr), PyUnicode_4BYTE_KIND); - - assert!(!_PyUnicode_COMPACT_DATA(ptr).is_null()); - // _PyUnicode_NONCOMPACT_DATA isn't valid for compact strings. - assert!(!PyUnicode_DATA(ptr).is_null()); - - assert_eq!(PyUnicode_GET_LENGTH(ptr), py_string.len().unwrap() as _); - assert_eq!(PyUnicode_IS_READY(ptr), 1); - - // This has potential to mutate object. But it should be a no-op since - // we're already ready. - assert_eq!(PyUnicode_READY(ptr), 0); - } - }) - } -} diff --git a/src/ffi/datetime.rs b/pyo3-ffi/src/datetime.rs similarity index 72% rename from src/ffi/datetime.rs rename to pyo3-ffi/src/datetime.rs index c7d7b787..d6499632 100644 --- a/src/ffi/datetime.rs +++ b/pyo3-ffi/src/datetime.rs @@ -9,15 +9,13 @@ //! Support for `PyDateTime_CAPI` is limited as of PyPy 7.0.0. //! `DateTime_FromTimestamp` and `Date_FromTimestamp` are currently not supported. -use crate::ffi::{PyObject, PyTypeObject}; -use crate::ffi::{PyObject_TypeCheck, Py_TYPE}; -use crate::once_cell::GILOnceCell; -use crate::Python; -use std::ops::Deref; +use crate::{PyObject, PyObject_TypeCheck, PyTypeObject, Py_TYPE}; +use std::cell::UnsafeCell; use std::os::raw::{c_char, c_int, c_uchar}; +use std::ptr; #[cfg(not(PyPy))] use { - crate::ffi::{PyCapsule_Import, Py_hash_t}, + crate::{PyCapsule_Import, Py_hash_t}, std::ffi::CString, }; @@ -434,62 +432,38 @@ pub struct PyDateTime_CAPI { // Python already shares this object between threads, so it's no more evil for us to do it too! unsafe impl Sync for PyDateTime_CAPI {} -/// Safe wrapper around the Python datetime C-API global. Note that this object differs slightly -/// from the equivalent C object: in C, this is implemented as a `static PyDateTime_CAPI *`. Here -/// this is implemented as a wrapper which implements [`Deref`] to access a reference to a -/// [`PyDateTime_CAPI`] object. +/// Returns a pointer to a `PyDateTime_CAPI` instance /// -/// In the [`Deref`] implementation, if the underlying object has not yet been initialized, the GIL -/// will automatically be acquired and [`PyDateTime_IMPORT()`] called. -pub static PyDateTimeAPI: _PyDateTimeAPI_impl = _PyDateTimeAPI_impl { - inner: GILOnceCell::new(), -}; +/// # Note +/// This function will return a null pointer until +/// `PyDateTime_IMPORT` is called +#[inline] +pub unsafe fn PyDateTimeAPI() -> *mut PyDateTime_CAPI { + *PyDateTimeAPI_impl.0.get() +} -/// Safe wrapper around the Python C-API global `PyDateTime_TimeZone_UTC`. This follows a similar -/// strategy as [`PyDateTimeAPI`]: the Python datetime C-API will automatically be imported if this -/// type is deferenced. -/// -/// The type obtained by dereferencing this object is `&'static PyObject`. This may change in the -/// future to be a more specific type representing that this is a `datetime.timezone` object. #[cfg(not(all(PyPy, not(Py_3_8))))] -pub static PyDateTime_TimeZone_UTC: _PyDateTime_TimeZone_UTC_impl = _PyDateTime_TimeZone_UTC_impl { - inner: &PyDateTimeAPI, -}; +#[inline] +pub unsafe fn PyDateTime_TimeZone_UTC() -> *mut PyObject { + (*PyDateTimeAPI()).TimeZone_UTC +} /// Populates the `PyDateTimeAPI` object -/// -/// Unlike in C, this does *not* need to be actively invoked in Rust, which -/// will populate the `PyDateTimeAPI` struct automatically on first use. -/// Use this function only if you want to eagerly load the datetime module, -/// such as if you do not want the first call to a datetime function to be -/// slightly slower than subsequent calls. -/// -/// # Safety -/// The Python GIL must be held. -pub unsafe fn PyDateTime_IMPORT() -> &'static PyDateTime_CAPI { - PyDateTimeAPI - .inner - .get_or_init(Python::assume_gil_acquired(), || { - // Because `get_or_init` is called with `assume_gil_acquired()`, it's necessary to acquire - // the GIL here to prevent segfault in usage of the safe `PyDateTimeAPI::deref` impl. - Python::with_gil(|_py| { - // PyPy expects the C-API to be initialized via PyDateTime_Import, so trying to use - // `PyCapsule_Import` will behave unexpectedly in pypy. - #[cfg(PyPy)] - let py_datetime_c_api = PyDateTime_Import(); +pub unsafe fn PyDateTime_IMPORT() { + // PyPy expects the C-API to be initialized via PyDateTime_Import, so trying to use + // `PyCapsule_Import` will behave unexpectedly in pypy. + #[cfg(PyPy)] + let py_datetime_c_api = PyDateTime_Import(); - #[cfg(not(PyPy))] - let py_datetime_c_api = { - // PyDateTime_CAPSULE_NAME is a macro in C - let PyDateTime_CAPSULE_NAME = CString::new("datetime.datetime_CAPI").unwrap(); + #[cfg(not(PyPy))] + let py_datetime_c_api = { + // PyDateTime_CAPSULE_NAME is a macro in C + let PyDateTime_CAPSULE_NAME = CString::new("datetime.datetime_CAPI").unwrap(); - &*(PyCapsule_Import(PyDateTime_CAPSULE_NAME.as_ptr(), 1) - as *const PyDateTime_CAPI) - }; + PyCapsule_Import(PyDateTime_CAPSULE_NAME.as_ptr(), 1) as *mut PyDateTime_CAPI + }; - py_datetime_c_api - }) - }) + *PyDateTimeAPI_impl.0.get() = py_datetime_c_api; } // skipped non-limited PyDateTime_TimeZone_UTC @@ -502,61 +476,61 @@ pub unsafe fn PyDateTime_IMPORT() -> &'static PyDateTime_CAPI { #[inline] /// Check if `op` is a `PyDateTimeAPI.DateType` or subtype. pub unsafe fn PyDate_Check(op: *mut PyObject) -> c_int { - PyObject_TypeCheck(op, PyDateTimeAPI.DateType) as c_int + PyObject_TypeCheck(op, (*PyDateTimeAPI()).DateType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.DateType`. pub unsafe fn PyDate_CheckExact(op: *mut PyObject) -> c_int { - (Py_TYPE(op) == PyDateTimeAPI.DateType) as c_int + (Py_TYPE(op) == (*PyDateTimeAPI()).DateType) as c_int } #[inline] /// Check if `op` is a `PyDateTimeAPI.DateTimeType` or subtype. pub unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int { - PyObject_TypeCheck(op, PyDateTimeAPI.DateTimeType) as c_int + PyObject_TypeCheck(op, (*PyDateTimeAPI()).DateTimeType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.DateTimeType`. pub unsafe fn PyDateTime_CheckExact(op: *mut PyObject) -> c_int { - (Py_TYPE(op) == PyDateTimeAPI.DateTimeType) as c_int + (Py_TYPE(op) == (*PyDateTimeAPI()).DateTimeType) as c_int } #[inline] /// Check if `op` is a `PyDateTimeAPI.TimeType` or subtype. pub unsafe fn PyTime_Check(op: *mut PyObject) -> c_int { - PyObject_TypeCheck(op, PyDateTimeAPI.TimeType) as c_int + PyObject_TypeCheck(op, (*PyDateTimeAPI()).TimeType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.TimeType`. pub unsafe fn PyTime_CheckExact(op: *mut PyObject) -> c_int { - (Py_TYPE(op) == PyDateTimeAPI.TimeType) as c_int + (Py_TYPE(op) == (*PyDateTimeAPI()).TimeType) as c_int } #[inline] /// Check if `op` is a `PyDateTimeAPI.DetaType` or subtype. pub unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int { - PyObject_TypeCheck(op, PyDateTimeAPI.DeltaType) as c_int + PyObject_TypeCheck(op, (*PyDateTimeAPI()).DeltaType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.DeltaType`. pub unsafe fn PyDelta_CheckExact(op: *mut PyObject) -> c_int { - (Py_TYPE(op) == PyDateTimeAPI.DeltaType) as c_int + (Py_TYPE(op) == (*PyDateTimeAPI()).DeltaType) as c_int } #[inline] /// Check if `op` is a `PyDateTimeAPI.TZInfoType` or subtype. pub unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int { - PyObject_TypeCheck(op, PyDateTimeAPI.TZInfoType) as c_int + PyObject_TypeCheck(op, (*PyDateTimeAPI()).TZInfoType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.TZInfoType`. pub unsafe fn PyTZInfo_CheckExact(op: *mut PyObject) -> c_int { - (Py_TYPE(op) == PyDateTimeAPI.TZInfoType) as c_int + (Py_TYPE(op) == (*PyDateTimeAPI()).TZInfoType) as c_int } // skipped non-limited PyDate_FromDate @@ -570,12 +544,14 @@ pub unsafe fn PyTZInfo_CheckExact(op: *mut PyObject) -> c_int { #[cfg(not(PyPy))] pub unsafe fn PyDateTime_FromTimestamp(args: *mut PyObject) -> *mut PyObject { - (PyDateTimeAPI.DateTime_FromTimestamp)(PyDateTimeAPI.DateTimeType, args, std::ptr::null_mut()) + let f = (*PyDateTimeAPI()).DateTime_FromTimestamp; + f((*PyDateTimeAPI()).DateTimeType, args, std::ptr::null_mut()) } #[cfg(not(PyPy))] pub unsafe fn PyDate_FromTimestamp(args: *mut PyObject) -> *mut PyObject { - (PyDateTimeAPI.Date_FromTimestamp)(PyDateTimeAPI.DateType, args) + let f = (*PyDateTimeAPI()).Date_FromTimestamp; + f((*PyDateTimeAPI()).DateType, args) } #[cfg(PyPy)] @@ -589,96 +565,13 @@ extern "C" { #[cfg(PyPy)] extern "C" { #[link_name = "_PyPyDateTime_Import"] - pub fn PyDateTime_Import() -> &'static PyDateTime_CAPI; + pub fn PyDateTime_Import() -> *mut PyDateTime_CAPI; } -// -- implementation details which are specific to Rust. -- +// Rust specific implementation details -#[doc(hidden)] -pub struct _PyDateTimeAPI_impl { - inner: GILOnceCell<&'static PyDateTime_CAPI>, -} +struct PyDateTimeAPISingleton(UnsafeCell<*mut PyDateTime_CAPI>); +unsafe impl Sync for PyDateTimeAPISingleton {} -impl Deref for _PyDateTimeAPI_impl { - type Target = PyDateTime_CAPI; - - #[inline] - fn deref(&self) -> &'static PyDateTime_CAPI { - unsafe { PyDateTime_IMPORT() } - } -} - -#[doc(hidden)] -#[cfg(not(all(PyPy, not(Py_3_8))))] -pub struct _PyDateTime_TimeZone_UTC_impl { - inner: &'static _PyDateTimeAPI_impl, -} - -#[cfg(not(all(PyPy, not(Py_3_8))))] -impl Deref for _PyDateTime_TimeZone_UTC_impl { - type Target = crate::PyObject; - - #[inline] - fn deref(&self) -> &crate::PyObject { - unsafe { - &*((&self.inner.TimeZone_UTC) as *const *mut crate::ffi::PyObject - as *const crate::PyObject) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{types::PyDict, AsPyPointer, IntoPy, Py, PyAny, Python}; - - #[test] - fn test_datetime_fromtimestamp() { - Python::with_gil(|py| { - let args: Py = (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 = (100,).into_py(py); - unsafe { PyDateTime_IMPORT() }; - let dt: &PyAny = unsafe { py.from_owned_ptr(PyDate_FromTimestamp(args.as_ptr())) }; - let locals = PyDict::new(py); - locals.set_item("dt", dt).unwrap(); - py.run( - "import datetime; assert dt == datetime.date.fromtimestamp(100)", - None, - Some(locals), - ) - .unwrap(); - }) - } - - #[test] - #[cfg(not(all(PyPy, not(Py_3_8))))] - fn test_utc_timezone() { - Python::with_gil(|py| { - let utc_timezone = PyDateTime_TimeZone_UTC.as_ref(py); - let locals = PyDict::new(py); - locals.set_item("utc_timezone", utc_timezone).unwrap(); - py.run( - "import datetime; assert utc_timezone is datetime.timezone.utc", - None, - Some(locals), - ) - .unwrap(); - }) - } -} +static PyDateTimeAPI_impl: PyDateTimeAPISingleton = + PyDateTimeAPISingleton(UnsafeCell::new(ptr::null_mut())); diff --git a/src/ffi/descrobject.rs b/pyo3-ffi/src/descrobject.rs similarity index 95% rename from src/ffi/descrobject.rs rename to pyo3-ffi/src/descrobject.rs index 6b6b0b2d..253b40dc 100644 --- a/src/ffi/descrobject.rs +++ b/pyo3-ffi/src/descrobject.rs @@ -1,6 +1,6 @@ -use crate::ffi::methodobject::PyMethodDef; -use crate::ffi::object::{PyObject, PyTypeObject}; -use crate::ffi::structmember::PyMemberDef; +use crate::methodobject::PyMethodDef; +use crate::object::{PyObject, PyTypeObject}; +use crate::structmember::PyMemberDef; use std::os::raw::{c_char, c_int, c_void}; pub type getter = unsafe extern "C" fn(slf: *mut PyObject, closure: *mut c_void) -> *mut PyObject; diff --git a/src/ffi/dictobject.rs b/pyo3-ffi/src/dictobject.rs similarity index 98% rename from src/ffi/dictobject.rs rename to pyo3-ffi/src/dictobject.rs index a0d2b2b3..c4cb7193 100644 --- a/src/ffi/dictobject.rs +++ b/pyo3-ffi/src/dictobject.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; use std::os::raw::{c_char, c_int}; #[cfg_attr(windows, link(name = "pythonXY"))] diff --git a/src/ffi/enumobject.rs b/pyo3-ffi/src/enumobject.rs similarity index 80% rename from src/ffi/enumobject.rs rename to pyo3-ffi/src/enumobject.rs index 6be7f1f3..e3f187d1 100644 --- a/src/ffi/enumobject.rs +++ b/pyo3-ffi/src/enumobject.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::PyTypeObject; +use crate::object::PyTypeObject; #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { diff --git a/src/ffi/eval.rs b/pyo3-ffi/src/eval.rs similarity index 95% rename from src/ffi/eval.rs rename to pyo3-ffi/src/eval.rs index 1ce62c07..612617c0 100644 --- a/src/ffi/eval.rs +++ b/pyo3-ffi/src/eval.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::PyObject; +use crate::object::PyObject; use std::os::raw::c_int; extern "C" { diff --git a/src/ffi/fileobject.rs b/pyo3-ffi/src/fileobject.rs similarity index 97% rename from src/ffi/fileobject.rs rename to pyo3-ffi/src/fileobject.rs index a72db985..525a7f10 100644 --- a/src/ffi/fileobject.rs +++ b/pyo3-ffi/src/fileobject.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::PyObject; +use crate::object::PyObject; use std::os::raw::{c_char, c_int}; pub const PY_STDIOTEXTMODE: &str = "b"; diff --git a/src/ffi/fileutils.rs b/pyo3-ffi/src/fileutils.rs similarity index 87% rename from src/ffi/fileutils.rs rename to pyo3-ffi/src/fileutils.rs index eb1de025..3f053b72 100644 --- a/src/ffi/fileutils.rs +++ b/pyo3-ffi/src/fileutils.rs @@ -1,4 +1,4 @@ -use crate::ffi::pyport::Py_ssize_t; +use crate::pyport::Py_ssize_t; use libc::wchar_t; use std::os::raw::c_char; diff --git a/src/ffi/floatobject.rs b/pyo3-ffi/src/floatobject.rs similarity index 98% rename from src/ffi/floatobject.rs rename to pyo3-ffi/src/floatobject.rs index d33feb9a..476f2145 100644 --- a/src/ffi/floatobject.rs +++ b/pyo3-ffi/src/floatobject.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::*; +use crate::object::*; use std::os::raw::{c_double, c_int}; #[cfg(Py_LIMITED_API)] diff --git a/src/ffi/funcobject.rs b/pyo3-ffi/src/funcobject.rs similarity index 96% rename from src/ffi/funcobject.rs rename to pyo3-ffi/src/funcobject.rs index f5ec3d87..020d2b49 100644 --- a/src/ffi/funcobject.rs +++ b/pyo3-ffi/src/funcobject.rs @@ -1,6 +1,6 @@ use std::os::raw::c_int; -use crate::ffi::object::{PyObject, PyTypeObject, Py_TYPE}; +use crate::object::{PyObject, PyTypeObject, Py_TYPE}; // skipped PyFunctionObject diff --git a/src/ffi/genobject.rs b/pyo3-ffi/src/genobject.rs similarity index 95% rename from src/ffi/genobject.rs rename to pyo3-ffi/src/genobject.rs index c486e202..054104de 100644 --- a/src/ffi/genobject.rs +++ b/pyo3-ffi/src/genobject.rs @@ -1,6 +1,6 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; -use crate::ffi::PyFrameObject; +use crate::object::*; +use crate::pyport::Py_ssize_t; +use crate::PyFrameObject; use std::os::raw::c_int; #[repr(C)] diff --git a/src/ffi/import.rs b/pyo3-ffi/src/import.rs similarity index 98% rename from src/ffi/import.rs rename to pyo3-ffi/src/import.rs index 0edb58cc..794e0ee5 100644 --- a/src/ffi/import.rs +++ b/pyo3-ffi/src/import.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::PyObject; +use crate::object::PyObject; use std::os::raw::{c_char, c_int, c_long}; extern "C" { diff --git a/src/ffi/intrcheck.rs b/pyo3-ffi/src/intrcheck.rs similarity index 100% rename from src/ffi/intrcheck.rs rename to pyo3-ffi/src/intrcheck.rs diff --git a/src/ffi/iterobject.rs b/pyo3-ffi/src/iterobject.rs similarity index 96% rename from src/ffi/iterobject.rs rename to pyo3-ffi/src/iterobject.rs index b41fa008..f83e30bb 100644 --- a/src/ffi/iterobject.rs +++ b/pyo3-ffi/src/iterobject.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::*; +use crate::object::*; use std::os::raw::c_int; #[cfg_attr(windows, link(name = "pythonXY"))] diff --git a/pyo3-ffi/src/lib.rs b/pyo3-ffi/src/lib.rs new file mode 100644 index 00000000..024f40b9 --- /dev/null +++ b/pyo3-ffi/src/lib.rs @@ -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::*; diff --git a/src/ffi/listobject.rs b/pyo3-ffi/src/listobject.rs similarity index 97% rename from src/ffi/listobject.rs rename to pyo3-ffi/src/listobject.rs index 79885bcf..c45b1fcb 100644 --- a/src/ffi/listobject.rs +++ b/pyo3-ffi/src/listobject.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; use std::os::raw::c_int; #[cfg_attr(windows, link(name = "pythonXY"))] diff --git a/src/ffi/longobject.rs b/pyo3-ffi/src/longobject.rs similarity index 98% rename from src/ffi/longobject.rs rename to pyo3-ffi/src/longobject.rs index 58d93d76..33f336af 100644 --- a/src/ffi/longobject.rs +++ b/pyo3-ffi/src/longobject.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; use libc::size_t; #[cfg(not(Py_LIMITED_API))] use std::os::raw::c_uchar; diff --git a/src/ffi/marshal.rs b/pyo3-ffi/src/marshal.rs similarity index 100% rename from src/ffi/marshal.rs rename to pyo3-ffi/src/marshal.rs diff --git a/src/ffi/memoryobject.rs b/pyo3-ffi/src/memoryobject.rs similarity index 95% rename from src/ffi/memoryobject.rs rename to pyo3-ffi/src/memoryobject.rs index 8ca27d79..1bebc881 100644 --- a/src/ffi/memoryobject.rs +++ b/pyo3-ffi/src/memoryobject.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; use std::os::raw::{c_char, c_int}; // skipped non-limited _PyManagedBuffer_Type diff --git a/src/ffi/methodobject.rs b/pyo3-ffi/src/methodobject.rs similarity index 95% rename from src/ffi/methodobject.rs rename to pyo3-ffi/src/methodobject.rs index 24dac732..af027407 100644 --- a/src/ffi/methodobject.rs +++ b/pyo3-ffi/src/methodobject.rs @@ -1,6 +1,6 @@ -use crate::ffi::object::{PyObject, PyTypeObject, Py_TYPE}; +use crate::object::{PyObject, PyTypeObject, Py_TYPE}; #[cfg(Py_3_9)] -use crate::ffi::PyObject_TypeCheck; +use crate::PyObject_TypeCheck; use std::mem; use std::os::raw::{c_char, c_int}; @@ -35,7 +35,7 @@ pub type PyCFunction = pub type _PyCFunctionFast = unsafe extern "C" fn( slf: *mut PyObject, args: *mut *mut PyObject, - nargs: crate::ffi::pyport::Py_ssize_t, + nargs: crate::pyport::Py_ssize_t, kwnames: *mut PyObject, ) -> *mut PyObject; @@ -49,7 +49,7 @@ pub type PyCFunctionWithKeywords = unsafe extern "C" fn( pub type _PyCFunctionFastWithKeywords = unsafe extern "C" fn( slf: *mut PyObject, args: *const *mut PyObject, - nargs: crate::ffi::pyport::Py_ssize_t, + nargs: crate::pyport::Py_ssize_t, kwnames: *mut PyObject, ) -> *mut PyObject; diff --git a/src/ffi/modsupport.rs b/pyo3-ffi/src/modsupport.rs similarity index 97% rename from src/ffi/modsupport.rs rename to pyo3-ffi/src/modsupport.rs index 30cba10e..fc0815a4 100644 --- a/src/ffi/modsupport.rs +++ b/pyo3-ffi/src/modsupport.rs @@ -1,7 +1,7 @@ -use crate::ffi::methodobject::PyMethodDef; -use crate::ffi::moduleobject::PyModuleDef; -use crate::ffi::object::PyObject; -use crate::ffi::pyport::Py_ssize_t; +use crate::methodobject::PyMethodDef; +use crate::moduleobject::PyModuleDef; +use crate::object::PyObject; +use crate::pyport::Py_ssize_t; use std::os::raw::{c_char, c_int, c_long}; extern "C" { diff --git a/src/ffi/moduleobject.rs b/pyo3-ffi/src/moduleobject.rs similarity index 96% rename from src/ffi/moduleobject.rs rename to pyo3-ffi/src/moduleobject.rs index 96d3e64b..94939943 100644 --- a/src/ffi/moduleobject.rs +++ b/pyo3-ffi/src/moduleobject.rs @@ -1,6 +1,6 @@ -use crate::ffi::methodobject::PyMethodDef; -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::methodobject::PyMethodDef; +use crate::object::*; +use crate::pyport::Py_ssize_t; use std::os::raw::{c_char, c_int, c_void}; #[cfg_attr(windows, link(name = "pythonXY"))] diff --git a/src/ffi/object.rs b/pyo3-ffi/src/object.rs similarity index 99% rename from src/ffi/object.rs rename to pyo3-ffi/src/object.rs index e113d1ee..ca9e180f 100644 --- a/src/ffi/object.rs +++ b/pyo3-ffi/src/object.rs @@ -1,6 +1,6 @@ // FFI note: this file changed a lot between 3.6 and 3.10. // Some missing definitions may not be marked "skipped". -use crate::ffi::pyport::{Py_hash_t, Py_ssize_t}; +use crate::pyport::{Py_hash_t, Py_ssize_t}; use std::mem; use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void}; use std::ptr; @@ -9,7 +9,7 @@ use std::ptr; opaque_struct!(PyTypeObject); #[cfg(not(Py_LIMITED_API))] -pub use crate::ffi::cpython::object::PyTypeObject; +pub use crate::cpython::object::PyTypeObject; // _PyObject_HEAD_EXTRA: conditionally defined in PyObject_HEAD_INIT // _PyObject_EXTRA_INIT: conditionally defined in PyObject_HEAD_INIT diff --git a/src/ffi/objimpl.rs b/pyo3-ffi/src/objimpl.rs similarity index 98% rename from src/ffi/objimpl.rs rename to pyo3-ffi/src/objimpl.rs index 8f805e3b..deeaceb1 100644 --- a/src/ffi/objimpl.rs +++ b/pyo3-ffi/src/objimpl.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; use libc::size_t; use std::os::raw::{c_int, c_void}; diff --git a/src/ffi/osmodule.rs b/pyo3-ffi/src/osmodule.rs similarity index 69% rename from src/ffi/osmodule.rs rename to pyo3-ffi/src/osmodule.rs index f15682bd..cacec443 100644 --- a/src/ffi/osmodule.rs +++ b/pyo3-ffi/src/osmodule.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::PyObject; +use crate::object::PyObject; extern "C" { pub fn PyOS_FSPath(path: *mut PyObject) -> *mut PyObject; diff --git a/src/ffi/pyarena.rs b/pyo3-ffi/src/pyarena.rs similarity index 100% rename from src/ffi/pyarena.rs rename to pyo3-ffi/src/pyarena.rs diff --git a/src/ffi/pycapsule.rs b/pyo3-ffi/src/pycapsule.rs similarity index 98% rename from src/ffi/pycapsule.rs rename to pyo3-ffi/src/pycapsule.rs index f93cf098..0e15d138 100644 --- a/src/ffi/pycapsule.rs +++ b/pyo3-ffi/src/pycapsule.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::*; +use crate::object::*; use std::os::raw::{c_char, c_int, c_void}; #[cfg_attr(windows, link(name = "pythonXY"))] diff --git a/src/ffi/pyerrors.rs b/pyo3-ffi/src/pyerrors.rs similarity index 99% rename from src/ffi/pyerrors.rs rename to pyo3-ffi/src/pyerrors.rs index 462d983d..3d3bfd4f 100644 --- a/src/ffi/pyerrors.rs +++ b/pyo3-ffi/src/pyerrors.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; use std::os::raw::{c_char, c_int}; #[repr(C)] @@ -158,7 +158,7 @@ pub unsafe fn PyUnicodeDecodeError_Create( end: Py_ssize_t, _reason: *const c_char, ) -> *mut PyObject { - crate::ffi::PyObject_CallFunction( + crate::PyObject_CallFunction( PyExc_UnicodeDecodeError, std::ffi::CStr::from_bytes_with_nul(b"sy#nns\0") .unwrap() diff --git a/src/ffi/pyframe.rs b/pyo3-ffi/src/pyframe.rs similarity index 87% rename from src/ffi/pyframe.rs rename to pyo3-ffi/src/pyframe.rs index e3c72268..c58bc1fb 100644 --- a/src/ffi/pyframe.rs +++ b/pyo3-ffi/src/pyframe.rs @@ -1,5 +1,5 @@ #[cfg(not(Py_LIMITED_API))] -use crate::ffi::PyFrameObject; +use crate::PyFrameObject; use std::os::raw::c_int; #[cfg(Py_LIMITED_API)] diff --git a/src/ffi/pyhash.rs b/pyo3-ffi/src/pyhash.rs similarity index 96% rename from src/ffi/pyhash.rs rename to pyo3-ffi/src/pyhash.rs index 703f6245..bfff7341 100644 --- a/src/ffi/pyhash.rs +++ b/pyo3-ffi/src/pyhash.rs @@ -1,5 +1,5 @@ #[cfg(not(Py_LIMITED_API))] -use crate::ffi::pyport::{Py_hash_t, Py_ssize_t}; +use crate::pyport::{Py_hash_t, Py_ssize_t}; #[cfg(not(Py_LIMITED_API))] use std::os::raw::{c_char, c_void}; diff --git a/src/ffi/pylifecycle.rs b/pyo3-ffi/src/pylifecycle.rs similarity index 97% rename from src/ffi/pylifecycle.rs rename to pyo3-ffi/src/pylifecycle.rs index 9970ceb9..7f73e3f0 100644 --- a/src/ffi/pylifecycle.rs +++ b/pyo3-ffi/src/pylifecycle.rs @@ -1,4 +1,4 @@ -use crate::ffi::pystate::PyThreadState; +use crate::pystate::PyThreadState; use libc::wchar_t; use std::os::raw::{c_char, c_int}; diff --git a/src/ffi/pymem.rs b/pyo3-ffi/src/pymem.rs similarity index 100% rename from src/ffi/pymem.rs rename to pyo3-ffi/src/pymem.rs diff --git a/src/ffi/pyport.rs b/pyo3-ffi/src/pyport.rs similarity index 100% rename from src/ffi/pyport.rs rename to pyo3-ffi/src/pyport.rs diff --git a/src/ffi/pystate.rs b/pyo3-ffi/src/pystate.rs similarity index 97% rename from src/ffi/pystate.rs rename to pyo3-ffi/src/pystate.rs index 4eb2049f..8bae6652 100644 --- a/src/ffi/pystate.rs +++ b/pyo3-ffi/src/pystate.rs @@ -1,6 +1,6 @@ #[cfg(not(PyPy))] -use crate::ffi::moduleobject::PyModuleDef; -use crate::ffi::object::PyObject; +use crate::moduleobject::PyModuleDef; +use crate::object::PyObject; use std::os::raw::c_int; #[cfg(not(PyPy))] diff --git a/src/ffi/pystrtod.rs b/pyo3-ffi/src/pystrtod.rs similarity index 97% rename from src/ffi/pystrtod.rs rename to pyo3-ffi/src/pystrtod.rs index 4937a88e..1f027686 100644 --- a/src/ffi/pystrtod.rs +++ b/pyo3-ffi/src/pystrtod.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::PyObject; +use crate::object::PyObject; use std::os::raw::{c_char, c_double, c_int}; extern "C" { diff --git a/src/ffi/pythonrun.rs b/pyo3-ffi/src/pythonrun.rs similarity index 93% rename from src/ffi/pythonrun.rs rename to pyo3-ffi/src/pythonrun.rs index 66f6703d..5045abc1 100644 --- a/src/ffi/pythonrun.rs +++ b/pyo3-ffi/src/pythonrun.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::*; +use crate::object::*; #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))] use libc::FILE; #[cfg(any(Py_LIMITED_API, not(Py_3_10)))] @@ -47,7 +47,7 @@ opaque_struct!(_node); #[inline] pub unsafe fn PyParser_SimpleParseString(s: *const c_char, b: c_int) -> *mut _node { #[allow(deprecated)] - crate::ffi::PyParser_SimpleParseStringFlags(s, b, 0) + crate::PyParser_SimpleParseStringFlags(s, b, 0) } #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))] @@ -55,7 +55,7 @@ pub unsafe fn PyParser_SimpleParseString(s: *const c_char, b: c_int) -> *mut _no #[inline] pub unsafe fn PyParser_SimpleParseFile(fp: *mut FILE, s: *const c_char, b: c_int) -> *mut _node { #[allow(deprecated)] - crate::ffi::PyParser_SimpleParseFileFlags(fp, s, b, 0) + crate::PyParser_SimpleParseFileFlags(fp, s, b, 0) } extern "C" { diff --git a/src/ffi/rangeobject.rs b/pyo3-ffi/src/rangeobject.rs similarity index 93% rename from src/ffi/rangeobject.rs rename to pyo3-ffi/src/rangeobject.rs index 1f2195bb..af2d3d0b 100644 --- a/src/ffi/rangeobject.rs +++ b/pyo3-ffi/src/rangeobject.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::*; +use crate::object::*; use std::os::raw::c_int; #[cfg_attr(windows, link(name = "pythonXY"))] diff --git a/src/ffi/setobject.rs b/pyo3-ffi/src/setobject.rs similarity index 96% rename from src/ffi/setobject.rs rename to pyo3-ffi/src/setobject.rs index 7471c5fe..b5e48592 100644 --- a/src/ffi/setobject.rs +++ b/pyo3-ffi/src/setobject.rs @@ -1,7 +1,7 @@ -use crate::ffi::object::*; +use crate::object::*; #[cfg(not(Py_LIMITED_API))] -use crate::ffi::pyport::Py_hash_t; -use crate::ffi::pyport::Py_ssize_t; +use crate::pyport::Py_hash_t; +use crate::pyport::Py_ssize_t; use std::os::raw::c_int; pub const PySet_MINSIZE: usize = 8; @@ -120,7 +120,7 @@ pub unsafe fn PyAnySet_Check(ob: *mut PyObject) -> c_int { #[inline] #[cfg(Py_3_10)] pub unsafe fn PySet_CheckExact(op: *mut PyObject) -> c_int { - crate::ffi::Py_IS_TYPE(op, &mut PySet_Type) + crate::Py_IS_TYPE(op, &mut PySet_Type) } extern "C" { diff --git a/src/ffi/sliceobject.rs b/pyo3-ffi/src/sliceobject.rs similarity index 97% rename from src/ffi/sliceobject.rs rename to pyo3-ffi/src/sliceobject.rs index 31488bb4..036b4c7d 100644 --- a/src/ffi/sliceobject.rs +++ b/pyo3-ffi/src/sliceobject.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; use std::os::raw::c_int; extern "C" { diff --git a/src/ffi/structmember.rs b/pyo3-ffi/src/structmember.rs similarity index 96% rename from src/ffi/structmember.rs rename to pyo3-ffi/src/structmember.rs index fc06e6da..4c94a206 100644 --- a/src/ffi/structmember.rs +++ b/pyo3-ffi/src/structmember.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::PyObject; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::PyObject; +use crate::pyport::Py_ssize_t; use std::os::raw::{c_char, c_int}; #[repr(C)] diff --git a/src/ffi/structseq.rs b/pyo3-ffi/src/structseq.rs similarity index 88% rename from src/ffi/structseq.rs rename to pyo3-ffi/src/structseq.rs index d0d45c79..8e66a95e 100644 --- a/src/ffi/structseq.rs +++ b/pyo3-ffi/src/structseq.rs @@ -1,6 +1,6 @@ -use crate::ffi::object::{PyObject, PyTypeObject}; +use crate::object::{PyObject, PyTypeObject}; #[cfg(not(PyPy))] -use crate::ffi::pyport::Py_ssize_t; +use crate::pyport::Py_ssize_t; use std::os::raw::{c_char, c_int}; #[repr(C)] @@ -40,18 +40,18 @@ extern "C" { } #[cfg(not(Py_LIMITED_API))] -pub type PyStructSequence = crate::ffi::PyTupleObject; +pub type PyStructSequence = crate::PyTupleObject; #[cfg(not(any(Py_LIMITED_API, PyPy)))] #[inline] pub unsafe fn PyStructSequence_SET_ITEM(op: *mut PyObject, i: Py_ssize_t, v: *mut PyObject) { - crate::ffi::PyTuple_SET_ITEM(op, i, v) + crate::PyTuple_SET_ITEM(op, i, v) } #[cfg(not(any(Py_LIMITED_API, PyPy)))] #[inline] pub unsafe fn PyStructSequence_GET_ITEM(op: *mut PyObject, i: Py_ssize_t) -> *mut PyObject { - crate::ffi::PyTuple_GET_ITEM(op, i) + crate::PyTuple_GET_ITEM(op, i) } extern "C" { diff --git a/src/ffi/sysmodule.rs b/pyo3-ffi/src/sysmodule.rs similarity index 97% rename from src/ffi/sysmodule.rs rename to pyo3-ffi/src/sysmodule.rs index 24987029..3c552254 100644 --- a/src/ffi/sysmodule.rs +++ b/pyo3-ffi/src/sysmodule.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::PyObject; +use crate::object::PyObject; use libc::wchar_t; use std::os::raw::{c_char, c_int}; diff --git a/src/ffi/traceback.rs b/pyo3-ffi/src/traceback.rs similarity index 86% rename from src/ffi/traceback.rs rename to pyo3-ffi/src/traceback.rs index e08f9c35..f9398fb6 100644 --- a/src/ffi/traceback.rs +++ b/pyo3-ffi/src/traceback.rs @@ -1,9 +1,9 @@ -use crate::ffi::object::*; +use crate::object::*; use std::os::raw::c_int; extern "C" { #[cfg_attr(PyPy, link_name = "PyPyTraceBack_Here")] - pub fn PyTraceBack_Here(arg1: *mut crate::ffi::PyFrameObject) -> c_int; + pub fn PyTraceBack_Here(arg1: *mut crate::PyFrameObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyTraceBack_Print")] pub fn PyTraceBack_Print(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; } diff --git a/src/ffi/tupleobject.rs b/pyo3-ffi/src/tupleobject.rs similarity index 95% rename from src/ffi/tupleobject.rs rename to pyo3-ffi/src/tupleobject.rs index 17198b39..e4f4b9ea 100644 --- a/src/ffi/tupleobject.rs +++ b/pyo3-ffi/src/tupleobject.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; use std::os::raw::c_int; #[cfg_attr(windows, link(name = "pythonXY"))] diff --git a/src/ffi/typeslots.rs b/pyo3-ffi/src/typeslots.rs similarity index 100% rename from src/ffi/typeslots.rs rename to pyo3-ffi/src/typeslots.rs diff --git a/src/ffi/unicodeobject.rs b/pyo3-ffi/src/unicodeobject.rs similarity index 99% rename from src/ffi/unicodeobject.rs rename to pyo3-ffi/src/unicodeobject.rs index 10c32ca2..5dd2b3cb 100644 --- a/src/ffi/unicodeobject.rs +++ b/pyo3-ffi/src/unicodeobject.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::*; +use crate::pyport::Py_ssize_t; use libc::wchar_t; use std::os::raw::{c_char, c_int, c_void}; diff --git a/src/ffi/warnings.rs b/pyo3-ffi/src/warnings.rs similarity index 92% rename from src/ffi/warnings.rs rename to pyo3-ffi/src/warnings.rs index 2e2731cc..488a23e7 100644 --- a/src/ffi/warnings.rs +++ b/pyo3-ffi/src/warnings.rs @@ -1,5 +1,5 @@ -use crate::ffi::object::PyObject; -use crate::ffi::pyport::Py_ssize_t; +use crate::object::PyObject; +use crate::pyport::Py_ssize_t; use std::os::raw::{c_char, c_int}; extern "C" { diff --git a/src/ffi/weakrefobject.rs b/pyo3-ffi/src/weakrefobject.rs similarity index 98% rename from src/ffi/weakrefobject.rs rename to pyo3-ffi/src/weakrefobject.rs index 19e12da6..575bc495 100644 --- a/src/ffi/weakrefobject.rs +++ b/pyo3-ffi/src/weakrefobject.rs @@ -1,4 +1,4 @@ -use crate::ffi::object::*; +use crate::object::*; use std::os::raw::c_int; opaque_struct!(PyWeakReference); diff --git a/src/ffi/README.md b/src/ffi/README.md deleted file mode 100644 index f65ba028..00000000 --- a/src/ffi/README.md +++ /dev/null @@ -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/]. - diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 82e59e99..81d6f38e 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -20,190 +20,12 @@ //! for more information. //! //! [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 -// 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]); - }; -} +#[cfg(all(not(Py_LIMITED_API), test))] +mod tests; -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::*; +// reexport raw bindings exposed in pyo3_ffi +pub use pyo3_ffi::*; /// Helper to enable #\[pymethods\] to see the workaround for __ipow__ on Python 3.7 #[doc(hidden)] diff --git a/src/ffi/tests.rs b/src/ffi/tests.rs new file mode 100644 index 00000000..15b1eb36 --- /dev/null +++ b/src/ffi/tests.rs @@ -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 = (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 = (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); + } + }) +} diff --git a/src/types/datetime.rs b/src/types/datetime.rs index 990d82ee..f6ca1fda 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -5,10 +5,9 @@ use crate::err::PyResult; use crate::ffi; -#[cfg(PyPy)] -use crate::ffi::datetime::{PyDateTime_FromTimestamp, PyDate_FromTimestamp}; -use crate::ffi::PyDateTimeAPI; -use crate::ffi::{PyDateTime_Check, PyDate_Check, PyDelta_Check, PyTZInfo_Check, PyTime_Check}; +use crate::ffi::{ + PyDateTime_CAPI, PyDateTime_FromTimestamp, PyDateTime_IMPORT, PyDate_FromTimestamp, +}; #[cfg(not(PyPy))] use crate::ffi::{PyDateTime_DATE_GET_FOLD, PyDateTime_TIME_GET_FOLD}; use crate::ffi::{ @@ -26,8 +25,64 @@ use crate::ffi::{ use crate::types::PyTuple; use crate::{AsPyPointer, PyAny, PyObject, Python, ToPyObject}; 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 @@ -111,7 +166,7 @@ pub struct PyDate(PyAny); pyobject_native_type!( PyDate, crate::ffi::PyDateTime_Date, - *PyDateTimeAPI.DateType, + *ensure_datetime_api(Python::assume_gil_acquired()).DateType, #module=Some("datetime"), #checkfunction=PyDate_Check ); @@ -120,11 +175,11 @@ impl PyDate { /// Creates a new `datetime.date`. pub fn new(py: Python, year: i32, month: u8, day: u8) -> PyResult<&PyDate> { unsafe { - let ptr = (PyDateTimeAPI.Date_FromDate)( + let ptr = (ensure_datetime_api(py).Date_FromDate)( year, c_int::from(month), c_int::from(day), - PyDateTimeAPI.DateType, + ensure_datetime_api(py).DateType, ); py.from_owned_ptr_or_err(ptr) } @@ -136,14 +191,11 @@ impl PyDate { pub fn from_timestamp(py: Python, timestamp: i64) -> PyResult<&PyDate> { let time_tuple = PyTuple::new(py, &[timestamp]); + // safety ensure that the API is loaded + let _api = ensure_datetime_api(py); + unsafe { - #[cfg(PyPy)] 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) } } @@ -169,7 +221,7 @@ pub struct PyDateTime(PyAny); pyobject_native_type!( PyDateTime, crate::ffi::PyDateTime_DateTime, - *PyDateTimeAPI.DateTimeType, + *ensure_datetime_api(Python::assume_gil_acquired()).DateType, #module=Some("datetime"), #checkfunction=PyDateTime_Check ); @@ -187,8 +239,9 @@ impl PyDateTime { microsecond: u32, tzinfo: Option<&PyObject>, ) -> PyResult<&'p PyDateTime> { + let api = ensure_datetime_api(py); unsafe { - let ptr = (PyDateTimeAPI.DateTime_FromDateAndTime)( + let ptr = (api.DateTime_FromDateAndTime)( year, c_int::from(month), c_int::from(day), @@ -197,7 +250,7 @@ impl PyDateTime { c_int::from(second), microsecond as c_int, opt_to_pyobj(py, tzinfo), - PyDateTimeAPI.DateTimeType, + api.DateTimeType, ); py.from_owned_ptr_or_err(ptr) } @@ -219,8 +272,9 @@ impl PyDateTime { tzinfo: Option<&PyObject>, fold: bool, ) -> PyResult<&'p PyDateTime> { + let api = ensure_datetime_api(py); unsafe { - let ptr = (PyDateTimeAPI.DateTime_FromDateAndTimeAndFold)( + let ptr = (api.DateTime_FromDateAndTimeAndFold)( year, c_int::from(month), c_int::from(day), @@ -230,7 +284,7 @@ impl PyDateTime { microsecond as c_int, opt_to_pyobj(py, tzinfo), c_int::from(fold), - PyDateTimeAPI.DateTimeType, + api.DateTimeType, ); py.from_owned_ptr_or_err(ptr) } @@ -253,19 +307,11 @@ impl PyDateTime { let args = PyTuple::new(py, &[timestamp, time_zone_info]); + // safety ensure API is loaded + let _api = ensure_datetime_api(py); + unsafe { - #[cfg(PyPy)] 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) } } @@ -314,7 +360,7 @@ pub struct PyTime(PyAny); pyobject_native_type!( PyTime, crate::ffi::PyDateTime_Time, - *PyDateTimeAPI.TimeType, + *ensure_datetime_api(Python::assume_gil_acquired()).TimeType, #module=Some("datetime"), #checkfunction=PyTime_Check ); @@ -329,14 +375,15 @@ impl PyTime { microsecond: u32, tzinfo: Option<&PyObject>, ) -> PyResult<&'p PyTime> { + let api = ensure_datetime_api(py); unsafe { - let ptr = (PyDateTimeAPI.Time_FromTime)( + let ptr = (api.Time_FromTime)( c_int::from(hour), c_int::from(minute), c_int::from(second), microsecond as c_int, opt_to_pyobj(py, tzinfo), - PyDateTimeAPI.TimeType, + api.TimeType, ); py.from_owned_ptr_or_err(ptr) } @@ -353,15 +400,16 @@ impl PyTime { tzinfo: Option<&PyObject>, fold: bool, ) -> PyResult<&'p PyTime> { + let api = ensure_datetime_api(py); unsafe { - let ptr = (PyDateTimeAPI.Time_FromTimeAndFold)( + let ptr = (api.Time_FromTimeAndFold)( c_int::from(hour), c_int::from(minute), c_int::from(second), microsecond as c_int, opt_to_pyobj(py, tzinfo), fold as c_int, - PyDateTimeAPI.TimeType, + api.TimeType, ); py.from_owned_ptr_or_err(ptr) } @@ -399,7 +447,7 @@ pub struct PyTzInfo(PyAny); pyobject_native_type!( PyTzInfo, crate::ffi::PyObject, - *PyDateTimeAPI.TZInfoType, + *ensure_datetime_api(Python::assume_gil_acquired()).TZInfoType, #module=Some("datetime"), #checkfunction=PyTZInfo_Check ); @@ -410,7 +458,7 @@ pub struct PyDelta(PyAny); pyobject_native_type!( PyDelta, crate::ffi::PyDateTime_Delta, - *PyDateTimeAPI.DeltaType, + *ensure_datetime_api(Python::assume_gil_acquired()).DeltaType, #module=Some("datetime"), #checkfunction=PyDelta_Check ); @@ -424,13 +472,14 @@ impl PyDelta { microseconds: i32, normalize: bool, ) -> PyResult<&PyDelta> { + let api = ensure_datetime_api(py); unsafe { - let ptr = (PyDateTimeAPI.Delta_FromDelta)( + let ptr = (api.Delta_FromDelta)( days as c_int, seconds as c_int, microseconds as c_int, normalize as c_int, - PyDateTimeAPI.DeltaType, + api.DeltaType, ); py.from_owned_ptr_or_err(ptr) } diff --git a/tests/test_datetime.rs b/tests/test_datetime.rs index 0cb5eddf..976c04ef 100644 --- a/tests/test_datetime.rs +++ b/tests/test_datetime.rs @@ -2,6 +2,7 @@ use pyo3::prelude::*; use pyo3::types::IntoPyDict; +use pyo3_ffi::PyDateTime_IMPORT; fn _get_subclasses<'p>( py: &'p Python, @@ -57,7 +58,7 @@ fn test_date_check() { let gil = Python::acquire_gil(); let py = gil.python(); 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_only!(PyDate_Check, PyDate_CheckExact, 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 py = gil.python(); 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_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") .map_err(|e| e.print(py)) .unwrap(); + unsafe { PyDateTime_IMPORT() } assert_check_only!(PyDate_Check, PyDate_CheckExact, obj); assert_check_exact!(PyDateTime_Check, PyDateTime_CheckExact, obj); @@ -93,6 +96,7 @@ fn test_delta_check() { let gil = Python::acquire_gil(); let py = gil.python(); 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_only!(PyDelta_Check, PyDelta_CheckExact, sub_obj); diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 2b919815..f6d9b4b6 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -107,6 +107,7 @@ fn llvm_cov_command(args: &[&str]) -> Command { "--package=pyo3-build-config", "--package=pyo3-macros-backend", "--package=pyo3-macros", + "--package=pyo3-ffi", ]) .args(args); command From 004fa2a68d41af9b386cec3876ffa30cbff10a97 Mon Sep 17 00:00:00 2001 From: DSPOM Date: Tue, 25 Jan 2022 20:25:55 +0100 Subject: [PATCH 2/2] fix: remove excess argument for `_PyCFunctionFast` --- pyo3-ffi/src/methodobject.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pyo3-ffi/src/methodobject.rs b/pyo3-ffi/src/methodobject.rs index af027407..6212090f 100644 --- a/pyo3-ffi/src/methodobject.rs +++ b/pyo3-ffi/src/methodobject.rs @@ -36,7 +36,6 @@ pub type _PyCFunctionFast = unsafe extern "C" fn( slf: *mut PyObject, args: *mut *mut PyObject, nargs: crate::pyport::Py_ssize_t, - kwnames: *mut PyObject, ) -> *mut PyObject; pub type PyCFunctionWithKeywords = unsafe extern "C" fn(