diff --git a/pytests/src/datetime.rs b/pytests/src/datetime.rs index 1407da3f..9d8f32a9 100644 --- a/pytests/src/datetime.rs +++ b/pytests/src/datetime.rs @@ -7,8 +7,8 @@ use pyo3::types::{ }; #[pyfunction] -fn make_date(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<&PyDate> { - PyDate::new(py, year, month, day) +fn make_date(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult> { + PyDate::new_bound(py, year, month, day) } #[pyfunction] @@ -17,34 +17,34 @@ fn get_date_tuple<'p>(py: Python<'p>, d: &PyDate) -> Bound<'p, PyTuple> { } #[pyfunction] -fn date_from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<&PyDate> { - PyDate::from_timestamp(py, timestamp) +fn date_from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult> { + PyDate::from_timestamp_bound(py, timestamp) } #[pyfunction] -fn make_time<'p>( - py: Python<'p>, +fn make_time<'py>( + py: Python<'py>, hour: u8, minute: u8, second: u8, microsecond: u32, - tzinfo: Option<&PyTzInfo>, -) -> PyResult<&'p PyTime> { - PyTime::new(py, hour, minute, second, microsecond, tzinfo) + tzinfo: Option<&Bound<'py, PyTzInfo>>, +) -> PyResult> { + PyTime::new_bound(py, hour, minute, second, microsecond, tzinfo) } #[pyfunction] #[pyo3(signature = (hour, minute, second, microsecond, tzinfo, fold))] -fn time_with_fold<'p>( - py: Python<'p>, +fn time_with_fold<'py>( + py: Python<'py>, hour: u8, minute: u8, second: u8, microsecond: u32, - tzinfo: Option<&PyTzInfo>, + tzinfo: Option<&Bound<'py, PyTzInfo>>, fold: bool, -) -> PyResult<&'p PyTime> { - PyTime::new_with_fold(py, hour, minute, second, microsecond, tzinfo, fold) +) -> PyResult> { + PyTime::new_bound_with_fold(py, hour, minute, second, microsecond, tzinfo, fold) } #[pyfunction] @@ -75,14 +75,19 @@ fn get_time_tuple_fold<'p>(py: Python<'p>, dt: &PyTime) -> Bound<'p, PyTuple> { } #[pyfunction] -fn make_delta(py: Python<'_>, days: i32, seconds: i32, microseconds: i32) -> PyResult<&PyDelta> { - PyDelta::new(py, days, seconds, microseconds, true) +fn make_delta( + py: Python<'_>, + days: i32, + seconds: i32, + microseconds: i32, +) -> PyResult> { + PyDelta::new_bound(py, days, seconds, microseconds, true) } #[pyfunction] -fn get_delta_tuple<'p>(py: Python<'p>, delta: &PyDelta) -> Bound<'p, PyTuple> { +fn get_delta_tuple<'py>(delta: &Bound<'py, PyDelta>) -> Bound<'py, PyTuple> { PyTuple::new_bound( - py, + delta.py(), [ delta.get_days(), delta.get_seconds(), @@ -93,8 +98,8 @@ fn get_delta_tuple<'p>(py: Python<'p>, delta: &PyDelta) -> Bound<'p, PyTuple> { #[allow(clippy::too_many_arguments)] #[pyfunction] -fn make_datetime<'p>( - py: Python<'p>, +fn make_datetime<'py>( + py: Python<'py>, year: i32, month: u8, day: u8, @@ -102,9 +107,9 @@ fn make_datetime<'p>( minute: u8, second: u8, microsecond: u32, - tzinfo: Option<&PyTzInfo>, -) -> PyResult<&'p PyDateTime> { - PyDateTime::new( + tzinfo: Option<&Bound<'py, PyTzInfo>>, +) -> PyResult> { + PyDateTime::new_bound( py, year, month, @@ -118,7 +123,7 @@ fn make_datetime<'p>( } #[pyfunction] -fn get_datetime_tuple<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyTuple> { +fn get_datetime_tuple<'py>(py: Python<'py>, dt: &Bound<'py, PyDateTime>) -> Bound<'py, PyTuple> { PyTuple::new_bound( py, [ @@ -134,7 +139,10 @@ fn get_datetime_tuple<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyTuple> } #[pyfunction] -fn get_datetime_tuple_fold<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyTuple> { +fn get_datetime_tuple_fold<'py>( + py: Python<'py>, + dt: &Bound<'py, PyDateTime>, +) -> Bound<'py, PyTuple> { PyTuple::new_bound( py, [ @@ -151,21 +159,21 @@ fn get_datetime_tuple_fold<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyT } #[pyfunction] -fn datetime_from_timestamp<'p>( - py: Python<'p>, +fn datetime_from_timestamp<'py>( + py: Python<'py>, ts: f64, - tz: Option<&PyTzInfo>, -) -> PyResult<&'p PyDateTime> { - PyDateTime::from_timestamp(py, ts, tz) + tz: Option<&Bound<'py, PyTzInfo>>, +) -> PyResult> { + PyDateTime::from_timestamp_bound(py, ts, tz) } #[pyfunction] -fn get_datetime_tzinfo(dt: &PyDateTime) -> Option> { +fn get_datetime_tzinfo<'py>(dt: &Bound<'py, PyDateTime>) -> Option> { dt.get_tzinfo_bound() } #[pyfunction] -fn get_time_tzinfo(dt: &PyTime) -> Option> { +fn get_time_tzinfo<'py>(dt: &Bound<'py, PyTime>) -> Option> { dt.get_tzinfo_bound() } @@ -179,15 +187,19 @@ impl TzClass { TzClass {} } - fn utcoffset<'p>(&self, py: Python<'p>, _dt: &PyDateTime) -> PyResult<&'p PyDelta> { - PyDelta::new(py, 0, 3600, 0, true) + fn utcoffset<'py>( + &self, + py: Python<'py>, + _dt: &Bound<'py, PyDateTime>, + ) -> PyResult> { + PyDelta::new_bound(py, 0, 3600, 0, true) } - fn tzname(&self, _py: Python<'_>, _dt: &PyDateTime) -> String { + fn tzname(&self, _dt: &Bound<'_, PyDateTime>) -> String { String::from("+01:00") } - fn dst(&self, _py: Python<'_>, _dt: &PyDateTime) -> Option<&PyDelta> { + fn dst<'py>(&self, _dt: &Bound<'py, PyDateTime>) -> Option> { None } } diff --git a/src/conversions/chrono.rs b/src/conversions/chrono.rs index 55d49d71..80a83b23 100644 --- a/src/conversions/chrono.rs +++ b/src/conversions/chrono.rs @@ -47,14 +47,12 @@ use crate::types::any::PyAnyMethods; use crate::types::datetime::timezone_from_offset; #[cfg(not(Py_LIMITED_API))] use crate::types::{ - timezone_utc, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, - PyTzInfo, PyTzInfoAccess, + timezone_utc_bound, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, + PyTimeAccess, PyTzInfo, PyTzInfoAccess, }; #[cfg(Py_LIMITED_API)] use crate::{intern, DowncastError}; -use crate::{ - Bound, FromPyObject, IntoPy, PyAny, PyErr, PyNativeType, PyObject, PyResult, Python, ToPyObject, -}; +use crate::{Bound, FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject}; use chrono::offset::{FixedOffset, Utc}; use chrono::{ DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Offset, TimeZone, Timelike, @@ -81,7 +79,7 @@ impl ToPyObject for Duration { // We pass true as the `normalize` parameter since we'd need to do several checks here to // avoid that, and it shouldn't have a big performance impact. // The seconds and microseconds cast should never overflow since it's at most the number of seconds per day - PyDelta::new( + PyDelta::new_bound( py, days.try_into().unwrap_or(i32::MAX), secs.try_into().unwrap(), @@ -144,7 +142,7 @@ impl ToPyObject for NaiveDate { let DateArgs { year, month, day } = self.into(); #[cfg(not(Py_LIMITED_API))] { - PyDate::new(py, year, month, day) + PyDate::new_bound(py, year, month, day) .expect("failed to construct date") .into() } @@ -189,15 +187,16 @@ impl ToPyObject for NaiveTime { truncated_leap_second, } = self.into(); #[cfg(not(Py_LIMITED_API))] - let time = PyTime::new(py, hour, min, sec, micro, None).expect("Failed to construct time"); + let time = + PyTime::new_bound(py, hour, min, sec, micro, None).expect("Failed to construct time"); #[cfg(Py_LIMITED_API)] let time = DatetimeTypes::get(py) .time - .as_ref(py) + .bind(py) .call1((hour, min, sec, micro)) .expect("failed to construct datetime.time"); if truncated_leap_second { - warn_truncated_leap_second(time); + warn_truncated_leap_second(&time); } time.into() } @@ -264,7 +263,7 @@ impl ToPyObject for DateTime { // FIXME: convert to better timezone representation here than just convert to fixed offset // See https://github.com/PyO3/pyo3/issues/3266 let tz = self.offset().fix().to_object(py); - let tz = tz.downcast(py).unwrap(); + let tz = tz.bind(py).downcast().unwrap(); naive_datetime_to_py_datetime(py, &self.naive_local(), Some(tz)) } } @@ -310,9 +309,9 @@ impl ToPyObject for FixedOffset { #[cfg(not(Py_LIMITED_API))] { - let td = PyDelta::new(py, 0, seconds_offset, 0, true) + let td = PyDelta::new_bound(py, 0, seconds_offset, 0, true) .expect("failed to construct timedelta"); - timezone_from_offset(py, td) + timezone_from_offset(&td) .expect("Failed to construct PyTimezone") .into() } @@ -366,7 +365,7 @@ impl FromPyObject<'_> for FixedOffset { impl ToPyObject for Utc { fn to_object(&self, py: Python<'_>) -> PyObject { - timezone_utc(py).into() + timezone_utc_bound(py).into() } } @@ -378,7 +377,7 @@ impl IntoPy for Utc { impl FromPyObject<'_> for Utc { fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { - let py_utc = timezone_utc(ob.py()); + let py_utc = timezone_utc_bound(ob.py()); if ob.eq(py_utc)? { Ok(Utc) } else { @@ -430,8 +429,8 @@ impl From<&NaiveTime> for TimeArgs { fn naive_datetime_to_py_datetime( py: Python<'_>, naive_datetime: &NaiveDateTime, - #[cfg(not(Py_LIMITED_API))] tzinfo: Option<&PyTzInfo>, - #[cfg(Py_LIMITED_API)] tzinfo: Option<&PyAny>, + #[cfg(not(Py_LIMITED_API))] tzinfo: Option<&Bound<'_, PyTzInfo>>, + #[cfg(Py_LIMITED_API)] tzinfo: Option<&Bound<'_, PyAny>>, ) -> PyObject { let DateArgs { year, month, day } = (&naive_datetime.date()).into(); let TimeArgs { @@ -442,21 +441,21 @@ fn naive_datetime_to_py_datetime( truncated_leap_second, } = (&naive_datetime.time()).into(); #[cfg(not(Py_LIMITED_API))] - let datetime = PyDateTime::new(py, year, month, day, hour, min, sec, micro, tzinfo) + let datetime = PyDateTime::new_bound(py, year, month, day, hour, min, sec, micro, tzinfo) .expect("failed to construct datetime"); #[cfg(Py_LIMITED_API)] let datetime = DatetimeTypes::get(py) .datetime - .as_ref(py) + .bind(py) .call1((year, month, day, hour, min, sec, micro, tzinfo)) .expect("failed to construct datetime.datetime"); if truncated_leap_second { - warn_truncated_leap_second(datetime); + warn_truncated_leap_second(&datetime); } datetime.into() } -fn warn_truncated_leap_second(obj: &PyAny) { +fn warn_truncated_leap_second(obj: &Bound<'_, PyAny>) { let py = obj.py(); if let Err(e) = PyErr::warn( py, @@ -558,8 +557,8 @@ impl DatetimeTypes { } #[cfg(Py_LIMITED_API)] -fn timezone_utc(py: Python<'_>) -> &PyAny { - DatetimeTypes::get(py).timezone_utc.as_ref(py) +fn timezone_utc_bound(py: Python<'_>) -> Bound<'_, PyAny> { + DatetimeTypes::get(py).timezone_utc.bind(py).clone() } #[cfg(test)] @@ -913,7 +912,7 @@ mod tests { let minute = 8; let second = 9; let micro = 999_999; - let tz_utc = timezone_utc(py); + let tz_utc = timezone_utc_bound(py); let py_datetime = new_py_datetime_ob( py, "datetime", diff --git a/src/conversions/std/time.rs b/src/conversions/std/time.rs index 201dd67c..4e6009e3 100755 --- a/src/conversions/std/time.rs +++ b/src/conversions/std/time.rs @@ -4,7 +4,7 @@ use crate::types::any::PyAnyMethods; #[cfg(Py_LIMITED_API)] use crate::types::PyType; #[cfg(not(Py_LIMITED_API))] -use crate::types::{timezone_utc, PyDateTime, PyDelta, PyDeltaAccess}; +use crate::types::{timezone_utc_bound, PyDateTime, PyDelta, PyDeltaAccess}; #[cfg(Py_LIMITED_API)] use crate::Py; use crate::{ @@ -59,7 +59,7 @@ impl ToPyObject for Duration { #[cfg(not(Py_LIMITED_API))] { - PyDelta::new( + PyDelta::new_bound( py, days.try_into() .expect("Too large Rust duration for timedelta"), @@ -130,7 +130,18 @@ fn unix_epoch_py(py: Python<'_>) -> &PyObject { #[cfg(not(Py_LIMITED_API))] { Ok::<_, PyErr>( - PyDateTime::new(py, 1970, 1, 1, 0, 0, 0, 0, Some(timezone_utc(py)))?.into(), + PyDateTime::new_bound( + py, + 1970, + 1, + 1, + 0, + 0, + 0, + 0, + Some(&timezone_utc_bound(py)), + )? + .into(), ) } #[cfg(Py_LIMITED_API)] diff --git a/src/ffi/tests.rs b/src/ffi/tests.rs index 0735e456..610edb1c 100644 --- a/src/ffi/tests.rs +++ b/src/ffi/tests.rs @@ -77,11 +77,11 @@ fn test_utc_timezone() { #[cfg(feature = "macros")] #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons fn test_timezone_from_offset() { - use crate::types::PyDelta; + use crate::{ffi_ptr_ext::FfiPtrExt, types::PyDelta}; Python::with_gil(|py| { - let delta = PyDelta::new(py, 0, 100, 0, false).unwrap(); - let tz: &PyAny = unsafe { py.from_borrowed_ptr(PyTimeZone_FromOffset(delta.as_ptr())) }; + let delta = PyDelta::new_bound(py, 0, 100, 0, false).unwrap(); + let tz = unsafe { PyTimeZone_FromOffset(delta.as_ptr()).assume_owned(py) }; crate::py_run!( py, tz, @@ -95,16 +95,13 @@ fn test_timezone_from_offset() { #[cfg(feature = "macros")] #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons fn test_timezone_from_offset_and_name() { - use crate::types::PyDelta; + use crate::{ffi_ptr_ext::FfiPtrExt, types::PyDelta}; Python::with_gil(|py| { - let delta = PyDelta::new(py, 0, 100, 0, false).unwrap(); + let delta = PyDelta::new_bound(py, 0, 100, 0, false).unwrap(); let tzname = PyString::new_bound(py, "testtz"); - let tz: &PyAny = unsafe { - py.from_borrowed_ptr(PyTimeZone_FromOffsetAndName( - delta.as_ptr(), - tzname.as_ptr(), - )) + let tz = unsafe { + PyTimeZone_FromOffsetAndName(delta.as_ptr(), tzname.as_ptr()).assume_owned(py) }; crate::py_run!( py, @@ -253,36 +250,36 @@ fn ucs4() { #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons #[cfg(not(PyPy))] fn test_get_tzinfo() { - use crate::types::timezone_utc; + use crate::types::timezone_utc_bound; crate::Python::with_gil(|py| { use crate::types::{PyDateTime, PyTime}; use crate::PyAny; - let utc = timezone_utc(py); + let utc = &timezone_utc_bound(py); - let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(utc)).unwrap(); + let dt = PyDateTime::new_bound(py, 2018, 1, 1, 0, 0, 0, 0, Some(utc)).unwrap(); assert!( unsafe { py.from_borrowed_ptr::(PyDateTime_DATE_GET_TZINFO(dt.as_ptr())) } .is(utc) ); - let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap(); + let dt = PyDateTime::new_bound(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap(); assert!( unsafe { py.from_borrowed_ptr::(PyDateTime_DATE_GET_TZINFO(dt.as_ptr())) } .is_none() ); - let t = PyTime::new(py, 0, 0, 0, 0, Some(utc)).unwrap(); + let t = PyTime::new_bound(py, 0, 0, 0, 0, Some(utc)).unwrap(); assert!( unsafe { py.from_borrowed_ptr::(PyDateTime_TIME_GET_TZINFO(t.as_ptr())) } .is(utc) ); - let t = PyTime::new(py, 0, 0, 0, 0, None).unwrap(); + let t = PyTime::new_bound(py, 0, 0, 0, 0, None).unwrap(); assert!( unsafe { py.from_borrowed_ptr::(PyDateTime_TIME_GET_TZINFO(t.as_ptr())) } diff --git a/src/types/datetime.rs b/src/types/datetime.rs index 088e37d9..d46a2b77 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -21,6 +21,7 @@ use crate::ffi::{ }; use crate::ffi_ptr_ext::FfiPtrExt; use crate::instance::PyNativeType; +use crate::py_result_ext::PyResultExt; use crate::types::any::PyAnyMethods; use crate::types::PyTuple; use crate::{Bound, IntoPy, Py, PyAny, PyErr, Python}; @@ -201,27 +202,53 @@ pyobject_native_type!( ); impl PyDate { - /// Creates a new `datetime.date`. + /// Deprecated form of [`PyDate::new_bound`]. + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyDate::new` will be replaced by `PyDate::new_bound` in a future PyO3 version" + ) + )] pub fn new(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<&PyDate> { + Self::new_bound(py, year, month, day).map(Bound::into_gil_ref) + } + + /// Creates a new `datetime.date`. + pub fn new_bound(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult> { let api = ensure_datetime_api(py)?; unsafe { - let ptr = (api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType); - py.from_owned_ptr_or_err(ptr) + (api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType) + .assume_owned_or_err(py) + .downcast_into_unchecked() } } + /// Deprecated form of [`PyDate::from_timestamp_bound`]. + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyDate::from_timestamp` will be replaced by `PyDate::from_timestamp_bound` in a future PyO3 version" + ) + )] + pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<&PyDate> { + Self::from_timestamp_bound(py, timestamp).map(Bound::into_gil_ref) + } + /// Construct a `datetime.date` from a POSIX timestamp /// /// This is equivalent to `datetime.date.fromtimestamp` - pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<&PyDate> { + pub fn from_timestamp_bound(py: Python<'_>, timestamp: i64) -> PyResult> { let time_tuple = PyTuple::new_bound(py, [timestamp]); // safety ensure that the API is loaded let _api = ensure_datetime_api(py)?; unsafe { - let ptr = PyDate_FromTimestamp(time_tuple.as_ptr()); - py.from_owned_ptr_or_err(ptr) + PyDate_FromTimestamp(time_tuple.as_ptr()) + .assume_owned_or_err(py) + .downcast_into_unchecked() } } } @@ -266,10 +293,17 @@ pyobject_native_type!( ); impl PyDateTime { - /// Creates a new `datetime.datetime` object. + /// Deprecated form of [`PyDateTime::new_bound`]. + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyDateTime::new` will be replaced by `PyDateTime::new_bound` in a future PyO3 version" + ) + )] #[allow(clippy::too_many_arguments)] - pub fn new<'p>( - py: Python<'p>, + pub fn new<'py>( + py: Python<'py>, year: i32, month: u8, day: u8, @@ -277,11 +311,38 @@ impl PyDateTime { minute: u8, second: u8, microsecond: u32, - tzinfo: Option<&PyTzInfo>, - ) -> PyResult<&'p PyDateTime> { + tzinfo: Option<&'py PyTzInfo>, + ) -> PyResult<&'py PyDateTime> { + Self::new_bound( + py, + year, + month, + day, + hour, + minute, + second, + microsecond, + tzinfo.map(PyTzInfo::as_borrowed).as_deref(), + ) + .map(Bound::into_gil_ref) + } + + /// Creates a new `datetime.datetime` object. + #[allow(clippy::too_many_arguments)] + pub fn new_bound<'py>( + py: Python<'py>, + year: i32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, + microsecond: u32, + tzinfo: Option<&Bound<'py, PyTzInfo>>, + ) -> PyResult> { let api = ensure_datetime_api(py)?; unsafe { - let ptr = (api.DateTime_FromDateAndTime)( + (api.DateTime_FromDateAndTime)( year, c_int::from(month), c_int::from(day), @@ -291,11 +352,48 @@ impl PyDateTime { microsecond as c_int, opt_to_pyobj(tzinfo), api.DateTimeType, - ); - py.from_owned_ptr_or_err(ptr) + ) + .assume_owned_or_err(py) + .downcast_into_unchecked() } } + /// Deprecated form of [`PyDateTime::new_bound_with_fold`]. + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyDateTime::new_with_fold` will be replaced by `PyDateTime::new_bound_with_fold` in a future PyO3 version" + ) + )] + #[allow(clippy::too_many_arguments)] + pub fn new_with_fold<'py>( + py: Python<'py>, + year: i32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, + microsecond: u32, + tzinfo: Option<&'py PyTzInfo>, + fold: bool, + ) -> PyResult<&'py PyDateTime> { + Self::new_bound_with_fold( + py, + year, + month, + day, + hour, + minute, + second, + microsecond, + tzinfo.map(PyTzInfo::as_borrowed).as_deref(), + fold, + ) + .map(Bound::into_gil_ref) + } + /// Alternate constructor that takes a `fold` parameter. A `true` value for this parameter /// signifies this this datetime is the later of two moments with the same representation, /// during a repeated interval. @@ -304,8 +402,8 @@ impl PyDateTime { /// represented time is ambiguous. /// See [PEP 495](https://www.python.org/dev/peps/pep-0495/) for more detail. #[allow(clippy::too_many_arguments)] - pub fn new_with_fold<'p>( - py: Python<'p>, + pub fn new_bound_with_fold<'py>( + py: Python<'py>, year: i32, month: u8, day: u8, @@ -313,12 +411,12 @@ impl PyDateTime { minute: u8, second: u8, microsecond: u32, - tzinfo: Option<&PyTzInfo>, + tzinfo: Option<&Bound<'py, PyTzInfo>>, fold: bool, - ) -> PyResult<&'p PyDateTime> { + ) -> PyResult> { let api = ensure_datetime_api(py)?; unsafe { - let ptr = (api.DateTime_FromDateAndTimeAndFold)( + (api.DateTime_FromDateAndTimeAndFold)( year, c_int::from(month), c_int::from(day), @@ -329,27 +427,46 @@ impl PyDateTime { opt_to_pyobj(tzinfo), c_int::from(fold), api.DateTimeType, - ); - py.from_owned_ptr_or_err(ptr) + ) + .assume_owned_or_err(py) + .downcast_into_unchecked() } } + /// Deprecated form of [`PyDateTime::from_timestamp_bound`]. + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyDateTime::from_timestamp` will be replaced by `PyDateTime::from_timestamp_bound` in a future PyO3 version" + ) + )] + pub fn from_timestamp<'py>( + py: Python<'py>, + timestamp: f64, + tzinfo: Option<&'py PyTzInfo>, + ) -> PyResult<&'py PyDateTime> { + Self::from_timestamp_bound(py, timestamp, tzinfo.map(PyTzInfo::as_borrowed).as_deref()) + .map(Bound::into_gil_ref) + } + /// Construct a `datetime` object from a POSIX timestamp /// /// This is equivalent to `datetime.datetime.fromtimestamp` - pub fn from_timestamp<'p>( - py: Python<'p>, + pub fn from_timestamp_bound<'py>( + py: Python<'py>, timestamp: f64, - tzinfo: Option<&PyTzInfo>, - ) -> PyResult<&'p PyDateTime> { - let args: Py = (timestamp, tzinfo).into_py(py); + tzinfo: Option<&Bound<'py, PyTzInfo>>, + ) -> PyResult> { + let args = IntoPy::>::into_py((timestamp, tzinfo), py).into_bound(py); // safety ensure API is loaded let _api = ensure_datetime_api(py)?; unsafe { - let ptr = PyDateTime_FromTimestamp(args.as_ptr()); - py.from_owned_ptr_or_err(ptr) + PyDateTime_FromTimestamp(args.as_ptr()) + .assume_owned_or_err(py) + .downcast_into_unchecked() } } } @@ -463,42 +580,99 @@ pyobject_native_type!( ); impl PyTime { - /// Creates a new `datetime.time` object. - pub fn new<'p>( - py: Python<'p>, + /// Deprecated form of [`PyTime::new_bound`]. + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyTime::new` will be replaced by `PyTime::new_bound` in a future PyO3 version" + ) + )] + pub fn new<'py>( + py: Python<'py>, hour: u8, minute: u8, second: u8, microsecond: u32, - tzinfo: Option<&PyTzInfo>, - ) -> PyResult<&'p PyTime> { + tzinfo: Option<&'py PyTzInfo>, + ) -> PyResult<&'py PyTime> { + Self::new_bound( + py, + hour, + minute, + second, + microsecond, + tzinfo.map(PyTzInfo::as_borrowed).as_deref(), + ) + .map(Bound::into_gil_ref) + } + + /// Creates a new `datetime.time` object. + pub fn new_bound<'py>( + py: Python<'py>, + hour: u8, + minute: u8, + second: u8, + microsecond: u32, + tzinfo: Option<&Bound<'py, PyTzInfo>>, + ) -> PyResult> { let api = ensure_datetime_api(py)?; unsafe { - let ptr = (api.Time_FromTime)( + (api.Time_FromTime)( c_int::from(hour), c_int::from(minute), c_int::from(second), microsecond as c_int, opt_to_pyobj(tzinfo), api.TimeType, - ); - py.from_owned_ptr_or_err(ptr) + ) + .assume_owned_or_err(py) + .downcast_into_unchecked() } } - /// Alternate constructor that takes a `fold` argument. See [`PyDateTime::new_with_fold`]. - pub fn new_with_fold<'p>( - py: Python<'p>, + /// Deprecated form of [`PyTime::new_bound_with_fold`]. + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyTime::new_with_fold` will be replaced by `PyTime::new_bound_with_fold` in a future PyO3 version" + ) + )] + pub fn new_with_fold<'py>( + py: Python<'py>, hour: u8, minute: u8, second: u8, microsecond: u32, - tzinfo: Option<&PyTzInfo>, + tzinfo: Option<&'py PyTzInfo>, fold: bool, - ) -> PyResult<&'p PyTime> { + ) -> PyResult<&'py PyTime> { + Self::new_bound_with_fold( + py, + hour, + minute, + second, + microsecond, + tzinfo.map(PyTzInfo::as_borrowed).as_deref(), + fold, + ) + .map(Bound::into_gil_ref) + } + + /// Alternate constructor that takes a `fold` argument. See [`PyDateTime::new_with_fold`]. + pub fn new_bound_with_fold<'py>( + py: Python<'py>, + hour: u8, + minute: u8, + second: u8, + microsecond: u32, + tzinfo: Option<&Bound<'py, PyTzInfo>>, + fold: bool, + ) -> PyResult> { let api = ensure_datetime_api(py)?; unsafe { - let ptr = (api.Time_FromTimeAndFold)( + (api.Time_FromTimeAndFold)( c_int::from(hour), c_int::from(minute), c_int::from(second), @@ -506,8 +680,9 @@ impl PyTime { opt_to_pyobj(tzinfo), fold as c_int, api.TimeType, - ); - py.from_owned_ptr_or_err(ptr) + ) + .assume_owned_or_err(py) + .downcast_into_unchecked() } } } @@ -596,20 +771,45 @@ pyobject_native_type!( #checkfunction=PyTZInfo_Check ); -/// Equivalent to `datetime.timezone.utc` +/// Deprecated form of [`timezone_utc_bound`]. +#[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`timezone_utc` will be replaced by `timezone_utc_bound` in a future PyO3 version" + ) +)] pub fn timezone_utc(py: Python<'_>) -> &PyTzInfo { - unsafe { &*(expect_datetime_api(py).TimeZone_UTC as *const PyTzInfo) } + timezone_utc_bound(py).into_gil_ref() +} + +/// Equivalent to `datetime.timezone.utc` +pub fn timezone_utc_bound(py: Python<'_>) -> Bound<'_, PyTzInfo> { + // TODO: this _could_ have a borrowed form `timezone_utc_borrowed`, but that seems + // like an edge case optimization and we'd prefer in PyO3 0.21 to use `Bound` as + // much as possible + unsafe { + expect_datetime_api(py) + .TimeZone_UTC + .assume_borrowed(py) + .to_owned() + .downcast_into_unchecked() + } } /// Equivalent to `datetime.timezone` constructor /// /// Only used internally #[cfg(feature = "chrono")] -pub fn timezone_from_offset<'a>(py: Python<'a>, offset: &PyDelta) -> PyResult<&'a PyTzInfo> { +pub(crate) fn timezone_from_offset<'py>( + offset: &Bound<'py, PyDelta>, +) -> PyResult> { + let py = offset.py(); let api = ensure_datetime_api(py)?; unsafe { - let ptr = (api.TimeZone_FromTimeZone)(offset.as_ptr(), ptr::null_mut()); - py.from_owned_ptr_or_err(ptr) + (api.TimeZone_FromTimeZone)(offset.as_ptr(), ptr::null_mut()) + .assume_owned_or_err(py) + .downcast_into_unchecked() } } @@ -625,7 +825,14 @@ pyobject_native_type!( ); impl PyDelta { - /// Creates a new `timedelta`. + /// Deprecated form of [`PyDelta::new_bound`]. + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyDelta::new` will be replaced by `PyDelta::new_bound` in a future PyO3 version" + ) + )] pub fn new( py: Python<'_>, days: i32, @@ -633,16 +840,28 @@ impl PyDelta { microseconds: i32, normalize: bool, ) -> PyResult<&PyDelta> { + Self::new_bound(py, days, seconds, microseconds, normalize).map(Bound::into_gil_ref) + } + + /// Creates a new `timedelta`. + pub fn new_bound( + py: Python<'_>, + days: i32, + seconds: i32, + microseconds: i32, + normalize: bool, + ) -> PyResult> { let api = ensure_datetime_api(py)?; unsafe { - let ptr = (api.Delta_FromDelta)( + (api.Delta_FromDelta)( days as c_int, seconds as c_int, microseconds as c_int, normalize as c_int, api.DeltaType, - ); - py.from_owned_ptr_or_err(ptr) + ) + .assume_owned_or_err(py) + .downcast_into_unchecked() } } } @@ -677,7 +896,7 @@ impl PyDeltaAccess for Bound<'_, PyDelta> { // Utility function which returns a borrowed reference to either // the underlying tzinfo or None. -fn opt_to_pyobj(opt: Option<&PyTzInfo>) -> *mut ffi::PyObject { +fn opt_to_pyobj(opt: Option<&Bound<'_, PyTzInfo>>) -> *mut ffi::PyObject { match opt { Some(tzi) => tzi.as_ptr(), None => unsafe { ffi::Py_None() }, @@ -685,6 +904,7 @@ fn opt_to_pyobj(opt: Option<&PyTzInfo>) -> *mut ffi::PyObject { } #[cfg(test)] +#[cfg_attr(not(feature = "gil-refs"), allow(deprecated))] mod tests { use super::*; #[cfg(feature = "macros")] @@ -768,7 +988,7 @@ mod tests { fn test_timezone_from_offset() { Python::with_gil(|py| { assert!( - timezone_from_offset(py, PyDelta::new(py, 0, -3600, 0, true).unwrap()) + timezone_from_offset(&PyDelta::new_bound(py, 0, -3600, 0, true).unwrap()) .unwrap() .call_method1("utcoffset", ((),)) .unwrap() @@ -779,7 +999,7 @@ mod tests { ); assert!( - timezone_from_offset(py, PyDelta::new(py, 0, 3600, 0, true).unwrap()) + timezone_from_offset(&PyDelta::new_bound(py, 0, 3600, 0, true).unwrap()) .unwrap() .call_method1("utcoffset", ((),)) .unwrap() @@ -789,7 +1009,7 @@ mod tests { .unwrap() ); - timezone_from_offset(py, PyDelta::new(py, 1, 0, 0, true).unwrap()).unwrap_err(); + timezone_from_offset(&PyDelta::new_bound(py, 1, 0, 0, true).unwrap()).unwrap_err(); }) } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 5162a17b..da22b307 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -8,10 +8,13 @@ pub use self::capsule::PyCapsule; #[cfg(not(Py_LIMITED_API))] pub use self::code::PyCode; pub use self::complex::PyComplex; +#[allow(deprecated)] +#[cfg(not(Py_LIMITED_API))] +pub use self::datetime::timezone_utc; #[cfg(not(Py_LIMITED_API))] pub use self::datetime::{ - timezone_utc, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, - PyTzInfo, PyTzInfoAccess, + timezone_utc_bound, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, + PyTimeAccess, PyTzInfo, PyTzInfoAccess, }; pub use self::dict::{IntoPyDict, PyDict}; #[cfg(not(PyPy))] diff --git a/tests/test_datetime.rs b/tests/test_datetime.rs index 5d7b1485..de8efa9b 100644 --- a/tests/test_datetime.rs +++ b/tests/test_datetime.rs @@ -1,7 +1,7 @@ #![cfg(not(Py_LIMITED_API))] use pyo3::prelude::*; -use pyo3::types::{timezone_utc, IntoPyDict, PyDate, PyDateTime, PyTime}; +use pyo3::types::{timezone_utc_bound, IntoPyDict, PyDate, PyDateTime, PyTime}; use pyo3_ffi::PyDateTime_IMPORT; fn _get_subclasses<'py>( @@ -118,9 +118,9 @@ fn test_datetime_utc() { use pyo3::types::PyDateTime; Python::with_gil(|py| { - let utc = timezone_utc(py); + let utc = timezone_utc_bound(py); - let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(utc)).unwrap(); + let dt = PyDateTime::new_bound(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap(); let locals = [("dt", dt)].into_py_dict_bound(py); @@ -155,7 +155,7 @@ fn test_pydate_out_of_bounds() { Python::with_gil(|py| { for val in INVALID_DATES { let (year, month, day) = val; - let dt = PyDate::new(py, *year, *month, *day); + let dt = PyDate::new_bound(py, *year, *month, *day); dt.unwrap_err(); } }); @@ -168,7 +168,7 @@ fn test_pytime_out_of_bounds() { Python::with_gil(|py| { for val in INVALID_TIMES { let (hour, minute, second, microsecond) = val; - let dt = PyTime::new(py, *hour, *minute, *second, *microsecond, None); + let dt = PyTime::new_bound(py, *hour, *minute, *second, *microsecond, None); dt.unwrap_err(); } }); @@ -192,7 +192,7 @@ fn test_pydatetime_out_of_bounds() { let (date, time) = val; let (year, month, day) = date; let (hour, minute, second, microsecond) = time; - let dt = PyDateTime::new( + let dt = PyDateTime::new_bound( py, *year, *month, diff --git a/tests/test_datetime_import.rs b/tests/test_datetime_import.rs index fee99b07..bf0001b8 100644 --- a/tests/test_datetime_import.rs +++ b/tests/test_datetime_import.rs @@ -21,6 +21,6 @@ fn test_bad_datetime_module_panic() { .unwrap(); // This should panic because the "datetime" module is empty - PyDate::new(py, 2018, 1, 1).unwrap(); + PyDate::new_bound(py, 2018, 1, 1).unwrap(); }); }