From 96b4ef948e630fefc071d58d19d937c9fad957c6 Mon Sep 17 00:00:00 2001 From: DSPOM Date: Tue, 25 Jan 2022 21:57:52 +0100 Subject: [PATCH] create unsafe bindings for datetime API --- pyo3-ffi/src/datetime.rs | 139 +++++++++++++++++++++++++++++- pyo3-ffi/src/lib.rs | 34 ++++---- src/ffi/datetime.rs | 179 +++------------------------------------ src/ffi/mod.rs | 76 +++++++++++++++++ 4 files changed, 238 insertions(+), 190 deletions(-) diff --git a/pyo3-ffi/src/datetime.rs b/pyo3-ffi/src/datetime.rs index b5215743160..201345603ea 100644 --- a/pyo3-ffi/src/datetime.rs +++ b/pyo3-ffi/src/datetime.rs @@ -9,10 +9,14 @@ //! Support for `PyDateTime_CAPI` is limited as of PyPy 7.0.0. //! `DateTime_FromTimestamp` and `Date_FromTimestamp` are currently not supported. -#[cfg(not(PyPy))] -use crate::Py_hash_t; -use crate::{PyObject, PyTypeObject}; +use crate::{PyObject, PyObject_TypeCheck, PyTypeObject, Py_TYPE}; use std::os::raw::{c_char, c_int, c_uchar}; +use std::ptr; +#[cfg(not(PyPy))] +use { + crate::{PyCapsule_Import, Py_hash_t}, + std::ffi::CString, +}; // Type struct wrappers const _PyDateTime_DATE_DATASIZE: usize = 4; @@ -426,3 +430,132 @@ 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 {} + +pub static mut PyDateTimeAPI: *mut PyDateTime_CAPI = ptr::null_mut(); + +#[cfg(not(all(PyPy, not(Py_3_8))))] +pub unsafe fn PyDateTime_TimeZone_UTC() -> *mut PyObject { + (*PyDateTimeAPI).TimeZone_UTC +} + +/// Populates the `PyDateTimeAPI` object +pub unsafe fn PyDateTime_IMPORT() { + PyDateTimeAPI = { + // 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(); + + PyCapsule_Import(PyDateTime_CAPSULE_NAME.as_ptr(), 1) as *mut PyDateTime_CAPI + }; + + py_datetime_c_api + }; +} + +// skipped non-limited PyDateTime_TimeZone_UTC + +/// 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`. +#[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 +} + +#[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 +} + +#[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 +} + +#[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 +} + +#[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 +} + +#[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 +} + +#[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 +} + +#[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 +} + +#[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 +} + +#[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 +} + +// skipped non-limited PyDate_FromDate +// skipped non-limited PyDateTime_FromDateAndTime +// skipped non-limited PyDateTime_FromDateAndTimeAndFold +// skipped non-limited PyTime_FromTime +// skipped non-limited PyTime_FromTimeAndFold +// skipped non-limited PyDelta_FromDSU +// skipped non-limited PyTimeZone_FromOffset +// skipped non-limited PyTimeZone_FromOffsetAndName + +#[cfg(not(PyPy))] +pub unsafe fn PyDateTime_FromTimestamp(args: *mut PyObject) -> *mut PyObject { + 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 { + let f = (*PyDateTimeAPI).Date_FromTimestamp; + f((*PyDateTimeAPI).DateType, args) +} + +#[cfg(PyPy)] +extern "C" { + #[link_name = "PyPyDate_FromTimestamp"] + pub fn PyDate_FromTimestamp(args: *mut PyObject) -> *mut PyObject; + #[link_name = "PyPyDateTime_FromTimestamp"] + pub fn PyDateTime_FromTimestamp(args: *mut PyObject) -> *mut PyObject; +} + +#[cfg(PyPy)] +extern "C" { + #[link_name = "_PyPyDateTime_Import"] + pub fn PyDateTime_Import() -> *mut PyDateTime_CAPI; +} diff --git a/pyo3-ffi/src/lib.rs b/pyo3-ffi/src/lib.rs index 98bb614ef28..0adad372426 100644 --- a/pyo3-ffi/src/lib.rs +++ b/pyo3-ffi/src/lib.rs @@ -85,9 +85,9 @@ //! ```rust //! use std::intrinsics::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 { @@ -102,7 +102,7 @@ //! m_clear: None, //! m_free: None, //! }; -//! +//! //! let mptr = PyModule_Create(Box::into_raw(Box::new(init))); //! let version = env!("CARGO_PKG_VERSION"); //! PyModule_AddObject( @@ -110,14 +110,14 @@ //! "__version__\0".as_ptr() as *const c_char, //! PyUnicode_FromStringAndSize(version.as_ptr() as *const c_char, version.len() as isize), //! ); -//! +//! //! let wrapped_sum_as_string = PyMethodDef { //! ml_name: "sum_as_string\0".as_ptr() as *const c_char, //! ml_meth: Some(transmute::<_PyCFunctionFast, PyCFunction>(sum_as_string)), //! ml_flags: METH_FASTCALL, //! ml_doc: "returns the sum of two integers as a string".as_ptr() as *const c_char, //! }; -//! +//! //! PyModule_AddObject( //! mptr, //! "sum_as_string\0".as_ptr() as *const c_char, @@ -127,9 +127,9 @@ //! 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( @@ -138,12 +138,12 @@ //! 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, @@ -152,24 +152,24 @@ //! 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); -//! +//! //! 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); -//! +//! //! 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 { @@ -217,7 +217,7 @@ //! ```bash //! sudo apt install python3-dev //! ``` -//! +//! //! While most projects use the save wrapper provided by pyo3, //! you can take a look at the [`orjson`] as an example on how to use ffi directly. //! For those well versed in C and Rust the [tutorials] from the cpython documentation @@ -234,8 +234,6 @@ //! [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, diff --git a/src/ffi/datetime.rs b/src/ffi/datetime.rs index 4790f81fa32..c74366cc165 100644 --- a/src/ffi/datetime.rs +++ b/src/ffi/datetime.rs @@ -1,191 +1,32 @@ use std::ops::Deref; -use crate::once_cell::GILOnceCell; +use super::*; use crate::Python; -use libc::c_int; -use pyo3_ffi::*; - -#[cfg(not(PyPy))] -use {crate::ffi::PyCapsule_Import, std::ffi::CString}; - -/// 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. -/// -/// 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(), -}; - -/// 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, -}; - -/// 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(); - - #[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) - }; - - py_datetime_c_api - }) - }) -} - -// skipped non-limited PyDateTime_TimeZone_UTC - -/// 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`. -#[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 -} - -#[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 -} - -#[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 -} - -#[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 -} - -#[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 -} - -#[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 -} - -#[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 -} - -#[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 -} - -#[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 -} - -#[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 -} - -// skipped non-limited PyDate_FromDate -// skipped non-limited PyDateTime_FromDateAndTime -// skipped non-limited PyDateTime_FromDateAndTimeAndFold -// skipped non-limited PyTime_FromTime -// skipped non-limited PyTime_FromTimeAndFold -// skipped non-limited PyDelta_FromDSU -// skipped non-limited PyTimeZone_FromOffset -// skipped non-limited PyTimeZone_FromOffsetAndName - -#[cfg(not(PyPy))] -pub unsafe fn PyDateTime_FromTimestamp(args: *mut PyObject) -> *mut PyObject { - (PyDateTimeAPI.DateTime_FromTimestamp)(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) -} - -#[cfg(PyPy)] -extern "C" { - #[link_name = "PyPyDate_FromTimestamp"] - pub fn PyDate_FromTimestamp(args: *mut PyObject) -> *mut PyObject; - #[link_name = "PyPyDateTime_FromTimestamp"] - pub fn PyDateTime_FromTimestamp(args: *mut PyObject) -> *mut PyObject; -} - -#[cfg(PyPy)] -extern "C" { - #[link_name = "_PyPyDateTime_Import"] - pub fn PyDateTime_Import() -> &'static PyDateTime_CAPI; -} // -- implementation details which are specific to Rust. -- #[doc(hidden)] -pub struct _PyDateTimeAPI_impl { - inner: GILOnceCell<&'static PyDateTime_CAPI>, -} +pub struct _PyDateTimeAPI_impl {} impl Deref for _PyDateTimeAPI_impl { type Target = PyDateTime_CAPI; #[inline] fn deref(&self) -> &'static PyDateTime_CAPI { - unsafe { PyDateTime_IMPORT() } + Python::with_gil(|_py| unsafe { + if pyo3_ffi::PyDateTimeAPI.is_null() { + PyDateTime_IMPORT() + } + + &*PyDateTimeAPI + }) } } #[doc(hidden)] #[cfg(not(all(PyPy, not(Py_3_8))))] pub struct _PyDateTime_TimeZone_UTC_impl { - inner: &'static _PyDateTimeAPI_impl, + pub(super) inner: &'static _PyDateTimeAPI_impl, } #[cfg(not(all(PyPy, not(Py_3_8))))] diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 7ce23a37e1a..257fbfd847e 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -38,9 +38,85 @@ mod datetime; #[cfg(not(Py_LIMITED_API))] pub use datetime::*; +#[cfg(not(Py_LIMITED_API))] +use {libc::c_int, std::ops::Deref}; + // 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)] pub use crate::impl_::pymethods::ipowfunc; + +// Shadow raw ffi bindings for backwards compatability + +#[cfg(not(Py_LIMITED_API))] +pub static PyDateTimeAPI: _PyDateTimeAPI_impl = _PyDateTimeAPI_impl {}; + +#[cfg(not(Py_LIMITED_API))] +pub static PyDateTime_TimeZone_UTC: _PyDateTime_TimeZone_UTC_impl = _PyDateTime_TimeZone_UTC_impl { + inner: &PyDateTimeAPI, +}; + +#[cfg(not(Py_LIMITED_API))] +macro_rules! reexport_with_autoinit { + ($(#[$outer:meta] pub unsafe fn $name: ident($arg: ident: *mut PyObject) -> $ret: ty;)*) => { + $( + #[inline] + #[$outer] + /// Check if `op` is a `PyDateTimeAPI.DateType` or subtype. + pub unsafe fn $name($arg: *mut PyObject) -> $ret { + // Deref PyDateTimeAPI so that the mutable static is always initalized + let _ = PyDateTimeAPI.deref(); + pyo3_ffi::$name($arg) + } + )* + + + }; +} + +// 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`. + +#[cfg(not(Py_LIMITED_API))] +reexport_with_autoinit! { + /// Check if `op` is a `PyDateTimeAPI.DateType` or subtype. + pub unsafe fn PyDate_Check(op: *mut PyObject) -> c_int; + + /// Check if `op`'s type is exactly `PyDateTimeAPI.DateType`. + pub unsafe fn PyDate_CheckExact(op: *mut PyObject) -> c_int; + + /// Check if `op` is a `PyDateTimeAPI.DateTimeType` or subtype. + pub unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int; + + /// Check if `op`'s type is exactly `PyDateTimeAPI.DateTimeType`. + pub unsafe fn PyDateTime_CheckExact(op: *mut PyObject) -> c_int; + + /// Check if `op` is a `PyDateTimeAPI.TimeType` or subtype. + pub unsafe fn PyTime_Check(op: *mut PyObject) -> c_int; + + /// Check if `op`'s type is exactly `PyDateTimeAPI.TimeType`. + pub unsafe fn PyTime_CheckExact(op: *mut PyObject) -> c_int; + + /// Check if `op` is a `PyDateTimeAPI.DetaType` or subtype. + pub unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int; + + /// Check if `op`'s type is exactly `PyDateTimeAPI.DeltaType`. + pub unsafe fn PyDelta_CheckExact(op: *mut PyObject) -> c_int; + + /// Check if `op` is a `PyDateTimeAPI.TZInfoType` or subtype. + pub unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int; + + /// Check if `op`'s type is exactly `PyDateTimeAPI.TZInfoType`. + pub unsafe fn PyTZInfo_CheckExact(op: *mut PyObject) -> c_int; + + #[cfg(not(PyPy))] + pub unsafe fn PyDateTime_FromTimestamp(args: *mut PyObject) -> *mut PyObject; + + #[cfg(not(PyPy))] + pub unsafe fn PyDate_FromTimestamp(args: *mut PyObject) -> *mut PyObject; +}