add `_bound` constructors for datetime types (#3778)

* add `_bound` constructors for datetime types

* review: Icxolu feedback

* update uses of deprecated timezone_utc
This commit is contained in:
David Hewitt 2024-02-12 20:49:58 +00:00 committed by GitHub
parent 1279467d27
commit 5b9b76fe58
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 387 additions and 145 deletions

View File

@ -7,8 +7,8 @@ use pyo3::types::{
}; };
#[pyfunction] #[pyfunction]
fn make_date(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<&PyDate> { fn make_date(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<Bound<'_, PyDate>> {
PyDate::new(py, year, month, day) PyDate::new_bound(py, year, month, day)
} }
#[pyfunction] #[pyfunction]
@ -17,34 +17,34 @@ fn get_date_tuple<'p>(py: Python<'p>, d: &PyDate) -> Bound<'p, PyTuple> {
} }
#[pyfunction] #[pyfunction]
fn date_from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<&PyDate> { fn date_from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<Bound<'_, PyDate>> {
PyDate::from_timestamp(py, timestamp) PyDate::from_timestamp_bound(py, timestamp)
} }
#[pyfunction] #[pyfunction]
fn make_time<'p>( fn make_time<'py>(
py: Python<'p>, py: Python<'py>,
hour: u8, hour: u8,
minute: u8, minute: u8,
second: u8, second: u8,
microsecond: u32, microsecond: u32,
tzinfo: Option<&PyTzInfo>, tzinfo: Option<&Bound<'py, PyTzInfo>>,
) -> PyResult<&'p PyTime> { ) -> PyResult<Bound<'py, PyTime>> {
PyTime::new(py, hour, minute, second, microsecond, tzinfo) PyTime::new_bound(py, hour, minute, second, microsecond, tzinfo)
} }
#[pyfunction] #[pyfunction]
#[pyo3(signature = (hour, minute, second, microsecond, tzinfo, fold))] #[pyo3(signature = (hour, minute, second, microsecond, tzinfo, fold))]
fn time_with_fold<'p>( fn time_with_fold<'py>(
py: Python<'p>, py: Python<'py>,
hour: u8, hour: u8,
minute: u8, minute: u8,
second: u8, second: u8,
microsecond: u32, microsecond: u32,
tzinfo: Option<&PyTzInfo>, tzinfo: Option<&Bound<'py, PyTzInfo>>,
fold: bool, fold: bool,
) -> PyResult<&'p PyTime> { ) -> PyResult<Bound<'py, PyTime>> {
PyTime::new_with_fold(py, hour, minute, second, microsecond, tzinfo, fold) PyTime::new_bound_with_fold(py, hour, minute, second, microsecond, tzinfo, fold)
} }
#[pyfunction] #[pyfunction]
@ -75,14 +75,19 @@ fn get_time_tuple_fold<'p>(py: Python<'p>, dt: &PyTime) -> Bound<'p, PyTuple> {
} }
#[pyfunction] #[pyfunction]
fn make_delta(py: Python<'_>, days: i32, seconds: i32, microseconds: i32) -> PyResult<&PyDelta> { fn make_delta(
PyDelta::new(py, days, seconds, microseconds, true) py: Python<'_>,
days: i32,
seconds: i32,
microseconds: i32,
) -> PyResult<Bound<'_, PyDelta>> {
PyDelta::new_bound(py, days, seconds, microseconds, true)
} }
#[pyfunction] #[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( PyTuple::new_bound(
py, delta.py(),
[ [
delta.get_days(), delta.get_days(),
delta.get_seconds(), 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)] #[allow(clippy::too_many_arguments)]
#[pyfunction] #[pyfunction]
fn make_datetime<'p>( fn make_datetime<'py>(
py: Python<'p>, py: Python<'py>,
year: i32, year: i32,
month: u8, month: u8,
day: u8, day: u8,
@ -102,9 +107,9 @@ fn make_datetime<'p>(
minute: u8, minute: u8,
second: u8, second: u8,
microsecond: u32, microsecond: u32,
tzinfo: Option<&PyTzInfo>, tzinfo: Option<&Bound<'py, PyTzInfo>>,
) -> PyResult<&'p PyDateTime> { ) -> PyResult<Bound<'py, PyDateTime>> {
PyDateTime::new( PyDateTime::new_bound(
py, py,
year, year,
month, month,
@ -118,7 +123,7 @@ fn make_datetime<'p>(
} }
#[pyfunction] #[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( PyTuple::new_bound(
py, py,
[ [
@ -134,7 +139,10 @@ fn get_datetime_tuple<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyTuple>
} }
#[pyfunction] #[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( PyTuple::new_bound(
py, py,
[ [
@ -151,21 +159,21 @@ fn get_datetime_tuple_fold<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyT
} }
#[pyfunction] #[pyfunction]
fn datetime_from_timestamp<'p>( fn datetime_from_timestamp<'py>(
py: Python<'p>, py: Python<'py>,
ts: f64, ts: f64,
tz: Option<&PyTzInfo>, tz: Option<&Bound<'py, PyTzInfo>>,
) -> PyResult<&'p PyDateTime> { ) -> PyResult<Bound<'py, PyDateTime>> {
PyDateTime::from_timestamp(py, ts, tz) PyDateTime::from_timestamp_bound(py, ts, tz)
} }
#[pyfunction] #[pyfunction]
fn get_datetime_tzinfo(dt: &PyDateTime) -> Option<Bound<'_, PyTzInfo>> { fn get_datetime_tzinfo<'py>(dt: &Bound<'py, PyDateTime>) -> Option<Bound<'py, PyTzInfo>> {
dt.get_tzinfo_bound() dt.get_tzinfo_bound()
} }
#[pyfunction] #[pyfunction]
fn get_time_tzinfo(dt: &PyTime) -> Option<Bound<'_, PyTzInfo>> { fn get_time_tzinfo<'py>(dt: &Bound<'py, PyTime>) -> Option<Bound<'py, PyTzInfo>> {
dt.get_tzinfo_bound() dt.get_tzinfo_bound()
} }
@ -179,15 +187,19 @@ impl TzClass {
TzClass {} TzClass {}
} }
fn utcoffset<'p>(&self, py: Python<'p>, _dt: &PyDateTime) -> PyResult<&'p PyDelta> { fn utcoffset<'py>(
PyDelta::new(py, 0, 3600, 0, true) &self,
py: Python<'py>,
_dt: &Bound<'py, PyDateTime>,
) -> PyResult<Bound<'py, PyDelta>> {
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") String::from("+01:00")
} }
fn dst(&self, _py: Python<'_>, _dt: &PyDateTime) -> Option<&PyDelta> { fn dst<'py>(&self, _dt: &Bound<'py, PyDateTime>) -> Option<Bound<'py, PyDelta>> {
None None
} }
} }

View File

@ -47,14 +47,12 @@ use crate::types::any::PyAnyMethods;
use crate::types::datetime::timezone_from_offset; use crate::types::datetime::timezone_from_offset;
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
use crate::types::{ use crate::types::{
timezone_utc, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, timezone_utc_bound, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime,
PyTzInfo, PyTzInfoAccess, PyTimeAccess, PyTzInfo, PyTzInfoAccess,
}; };
#[cfg(Py_LIMITED_API)] #[cfg(Py_LIMITED_API)]
use crate::{intern, DowncastError}; use crate::{intern, DowncastError};
use crate::{ use crate::{Bound, FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject};
Bound, FromPyObject, IntoPy, PyAny, PyErr, PyNativeType, PyObject, PyResult, Python, ToPyObject,
};
use chrono::offset::{FixedOffset, Utc}; use chrono::offset::{FixedOffset, Utc};
use chrono::{ use chrono::{
DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Offset, TimeZone, Timelike, 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 // 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. // 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 // 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, py,
days.try_into().unwrap_or(i32::MAX), days.try_into().unwrap_or(i32::MAX),
secs.try_into().unwrap(), secs.try_into().unwrap(),
@ -144,7 +142,7 @@ impl ToPyObject for NaiveDate {
let DateArgs { year, month, day } = self.into(); let DateArgs { year, month, day } = self.into();
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
{ {
PyDate::new(py, year, month, day) PyDate::new_bound(py, year, month, day)
.expect("failed to construct date") .expect("failed to construct date")
.into() .into()
} }
@ -189,15 +187,16 @@ impl ToPyObject for NaiveTime {
truncated_leap_second, truncated_leap_second,
} = self.into(); } = self.into();
#[cfg(not(Py_LIMITED_API))] #[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)] #[cfg(Py_LIMITED_API)]
let time = DatetimeTypes::get(py) let time = DatetimeTypes::get(py)
.time .time
.as_ref(py) .bind(py)
.call1((hour, min, sec, micro)) .call1((hour, min, sec, micro))
.expect("failed to construct datetime.time"); .expect("failed to construct datetime.time");
if truncated_leap_second { if truncated_leap_second {
warn_truncated_leap_second(time); warn_truncated_leap_second(&time);
} }
time.into() time.into()
} }
@ -264,7 +263,7 @@ impl<Tz: TimeZone> ToPyObject for DateTime<Tz> {
// FIXME: convert to better timezone representation here than just convert to fixed offset // FIXME: convert to better timezone representation here than just convert to fixed offset
// See https://github.com/PyO3/pyo3/issues/3266 // See https://github.com/PyO3/pyo3/issues/3266
let tz = self.offset().fix().to_object(py); 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)) naive_datetime_to_py_datetime(py, &self.naive_local(), Some(tz))
} }
} }
@ -310,9 +309,9 @@ impl ToPyObject for FixedOffset {
#[cfg(not(Py_LIMITED_API))] #[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"); .expect("failed to construct timedelta");
timezone_from_offset(py, td) timezone_from_offset(&td)
.expect("Failed to construct PyTimezone") .expect("Failed to construct PyTimezone")
.into() .into()
} }
@ -366,7 +365,7 @@ impl FromPyObject<'_> for FixedOffset {
impl ToPyObject for Utc { impl ToPyObject for Utc {
fn to_object(&self, py: Python<'_>) -> PyObject { fn to_object(&self, py: Python<'_>) -> PyObject {
timezone_utc(py).into() timezone_utc_bound(py).into()
} }
} }
@ -378,7 +377,7 @@ impl IntoPy<PyObject> for Utc {
impl FromPyObject<'_> for Utc { impl FromPyObject<'_> for Utc {
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Utc> { fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Utc> {
let py_utc = timezone_utc(ob.py()); let py_utc = timezone_utc_bound(ob.py());
if ob.eq(py_utc)? { if ob.eq(py_utc)? {
Ok(Utc) Ok(Utc)
} else { } else {
@ -430,8 +429,8 @@ impl From<&NaiveTime> for TimeArgs {
fn naive_datetime_to_py_datetime( fn naive_datetime_to_py_datetime(
py: Python<'_>, py: Python<'_>,
naive_datetime: &NaiveDateTime, naive_datetime: &NaiveDateTime,
#[cfg(not(Py_LIMITED_API))] tzinfo: Option<&PyTzInfo>, #[cfg(not(Py_LIMITED_API))] tzinfo: Option<&Bound<'_, PyTzInfo>>,
#[cfg(Py_LIMITED_API)] tzinfo: Option<&PyAny>, #[cfg(Py_LIMITED_API)] tzinfo: Option<&Bound<'_, PyAny>>,
) -> PyObject { ) -> PyObject {
let DateArgs { year, month, day } = (&naive_datetime.date()).into(); let DateArgs { year, month, day } = (&naive_datetime.date()).into();
let TimeArgs { let TimeArgs {
@ -442,21 +441,21 @@ fn naive_datetime_to_py_datetime(
truncated_leap_second, truncated_leap_second,
} = (&naive_datetime.time()).into(); } = (&naive_datetime.time()).into();
#[cfg(not(Py_LIMITED_API))] #[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"); .expect("failed to construct datetime");
#[cfg(Py_LIMITED_API)] #[cfg(Py_LIMITED_API)]
let datetime = DatetimeTypes::get(py) let datetime = DatetimeTypes::get(py)
.datetime .datetime
.as_ref(py) .bind(py)
.call1((year, month, day, hour, min, sec, micro, tzinfo)) .call1((year, month, day, hour, min, sec, micro, tzinfo))
.expect("failed to construct datetime.datetime"); .expect("failed to construct datetime.datetime");
if truncated_leap_second { if truncated_leap_second {
warn_truncated_leap_second(datetime); warn_truncated_leap_second(&datetime);
} }
datetime.into() datetime.into()
} }
fn warn_truncated_leap_second(obj: &PyAny) { fn warn_truncated_leap_second(obj: &Bound<'_, PyAny>) {
let py = obj.py(); let py = obj.py();
if let Err(e) = PyErr::warn( if let Err(e) = PyErr::warn(
py, py,
@ -558,8 +557,8 @@ impl DatetimeTypes {
} }
#[cfg(Py_LIMITED_API)] #[cfg(Py_LIMITED_API)]
fn timezone_utc(py: Python<'_>) -> &PyAny { fn timezone_utc_bound(py: Python<'_>) -> Bound<'_, PyAny> {
DatetimeTypes::get(py).timezone_utc.as_ref(py) DatetimeTypes::get(py).timezone_utc.bind(py).clone()
} }
#[cfg(test)] #[cfg(test)]
@ -913,7 +912,7 @@ mod tests {
let minute = 8; let minute = 8;
let second = 9; let second = 9;
let micro = 999_999; let micro = 999_999;
let tz_utc = timezone_utc(py); let tz_utc = timezone_utc_bound(py);
let py_datetime = new_py_datetime_ob( let py_datetime = new_py_datetime_ob(
py, py,
"datetime", "datetime",

View File

@ -4,7 +4,7 @@ use crate::types::any::PyAnyMethods;
#[cfg(Py_LIMITED_API)] #[cfg(Py_LIMITED_API)]
use crate::types::PyType; use crate::types::PyType;
#[cfg(not(Py_LIMITED_API))] #[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)] #[cfg(Py_LIMITED_API)]
use crate::Py; use crate::Py;
use crate::{ use crate::{
@ -59,7 +59,7 @@ impl ToPyObject for Duration {
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
{ {
PyDelta::new( PyDelta::new_bound(
py, py,
days.try_into() days.try_into()
.expect("Too large Rust duration for timedelta"), .expect("Too large Rust duration for timedelta"),
@ -130,7 +130,18 @@ fn unix_epoch_py(py: Python<'_>) -> &PyObject {
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
{ {
Ok::<_, PyErr>( 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)] #[cfg(Py_LIMITED_API)]

View File

@ -77,11 +77,11 @@ fn test_utc_timezone() {
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
fn test_timezone_from_offset() { fn test_timezone_from_offset() {
use crate::types::PyDelta; use crate::{ffi_ptr_ext::FfiPtrExt, types::PyDelta};
Python::with_gil(|py| { 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 tz: &PyAny = unsafe { py.from_borrowed_ptr(PyTimeZone_FromOffset(delta.as_ptr())) }; let tz = unsafe { PyTimeZone_FromOffset(delta.as_ptr()).assume_owned(py) };
crate::py_run!( crate::py_run!(
py, py,
tz, tz,
@ -95,16 +95,13 @@ fn test_timezone_from_offset() {
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
fn test_timezone_from_offset_and_name() { fn test_timezone_from_offset_and_name() {
use crate::types::PyDelta; use crate::{ffi_ptr_ext::FfiPtrExt, types::PyDelta};
Python::with_gil(|py| { 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 tzname = PyString::new_bound(py, "testtz");
let tz: &PyAny = unsafe { let tz = unsafe {
py.from_borrowed_ptr(PyTimeZone_FromOffsetAndName( PyTimeZone_FromOffsetAndName(delta.as_ptr(), tzname.as_ptr()).assume_owned(py)
delta.as_ptr(),
tzname.as_ptr(),
))
}; };
crate::py_run!( crate::py_run!(
py, py,
@ -253,36 +250,36 @@ fn ucs4() {
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
#[cfg(not(PyPy))] #[cfg(not(PyPy))]
fn test_get_tzinfo() { fn test_get_tzinfo() {
use crate::types::timezone_utc; use crate::types::timezone_utc_bound;
crate::Python::with_gil(|py| { crate::Python::with_gil(|py| {
use crate::types::{PyDateTime, PyTime}; use crate::types::{PyDateTime, PyTime};
use crate::PyAny; 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!( assert!(
unsafe { py.from_borrowed_ptr::<PyAny>(PyDateTime_DATE_GET_TZINFO(dt.as_ptr())) } unsafe { py.from_borrowed_ptr::<PyAny>(PyDateTime_DATE_GET_TZINFO(dt.as_ptr())) }
.is(utc) .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!( assert!(
unsafe { py.from_borrowed_ptr::<PyAny>(PyDateTime_DATE_GET_TZINFO(dt.as_ptr())) } unsafe { py.from_borrowed_ptr::<PyAny>(PyDateTime_DATE_GET_TZINFO(dt.as_ptr())) }
.is_none() .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!( assert!(
unsafe { py.from_borrowed_ptr::<PyAny>(PyDateTime_TIME_GET_TZINFO(t.as_ptr())) } unsafe { py.from_borrowed_ptr::<PyAny>(PyDateTime_TIME_GET_TZINFO(t.as_ptr())) }
.is(utc) .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!( assert!(
unsafe { py.from_borrowed_ptr::<PyAny>(PyDateTime_TIME_GET_TZINFO(t.as_ptr())) } unsafe { py.from_borrowed_ptr::<PyAny>(PyDateTime_TIME_GET_TZINFO(t.as_ptr())) }

View File

@ -21,6 +21,7 @@ use crate::ffi::{
}; };
use crate::ffi_ptr_ext::FfiPtrExt; use crate::ffi_ptr_ext::FfiPtrExt;
use crate::instance::PyNativeType; use crate::instance::PyNativeType;
use crate::py_result_ext::PyResultExt;
use crate::types::any::PyAnyMethods; use crate::types::any::PyAnyMethods;
use crate::types::PyTuple; use crate::types::PyTuple;
use crate::{Bound, IntoPy, Py, PyAny, PyErr, Python}; use crate::{Bound, IntoPy, Py, PyAny, PyErr, Python};
@ -201,27 +202,53 @@ pyobject_native_type!(
); );
impl PyDate { 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> { 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<Bound<'_, PyDate>> {
let api = ensure_datetime_api(py)?; let api = ensure_datetime_api(py)?;
unsafe { unsafe {
let ptr = (api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType); (api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType)
py.from_owned_ptr_or_err(ptr) .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 /// Construct a `datetime.date` from a POSIX timestamp
/// ///
/// This is equivalent to `datetime.date.fromtimestamp` /// 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<Bound<'_, PyDate>> {
let time_tuple = PyTuple::new_bound(py, [timestamp]); let time_tuple = PyTuple::new_bound(py, [timestamp]);
// safety ensure that the API is loaded // safety ensure that the API is loaded
let _api = ensure_datetime_api(py)?; let _api = ensure_datetime_api(py)?;
unsafe { unsafe {
let ptr = PyDate_FromTimestamp(time_tuple.as_ptr()); PyDate_FromTimestamp(time_tuple.as_ptr())
py.from_owned_ptr_or_err(ptr) .assume_owned_or_err(py)
.downcast_into_unchecked()
} }
} }
} }
@ -266,10 +293,17 @@ pyobject_native_type!(
); );
impl PyDateTime { 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)] #[allow(clippy::too_many_arguments)]
pub fn new<'p>( pub fn new<'py>(
py: Python<'p>, py: Python<'py>,
year: i32, year: i32,
month: u8, month: u8,
day: u8, day: u8,
@ -277,11 +311,38 @@ impl PyDateTime {
minute: u8, minute: u8,
second: u8, second: u8,
microsecond: u32, microsecond: u32,
tzinfo: Option<&PyTzInfo>, tzinfo: Option<&'py PyTzInfo>,
) -> PyResult<&'p PyDateTime> { ) -> 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<Bound<'py, PyDateTime>> {
let api = ensure_datetime_api(py)?; let api = ensure_datetime_api(py)?;
unsafe { unsafe {
let ptr = (api.DateTime_FromDateAndTime)( (api.DateTime_FromDateAndTime)(
year, year,
c_int::from(month), c_int::from(month),
c_int::from(day), c_int::from(day),
@ -291,11 +352,48 @@ impl PyDateTime {
microsecond as c_int, microsecond as c_int,
opt_to_pyobj(tzinfo), opt_to_pyobj(tzinfo),
api.DateTimeType, 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 /// 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, /// signifies this this datetime is the later of two moments with the same representation,
/// during a repeated interval. /// during a repeated interval.
@ -304,8 +402,8 @@ impl PyDateTime {
/// represented time is ambiguous. /// represented time is ambiguous.
/// See [PEP 495](https://www.python.org/dev/peps/pep-0495/) for more detail. /// See [PEP 495](https://www.python.org/dev/peps/pep-0495/) for more detail.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new_with_fold<'p>( pub fn new_bound_with_fold<'py>(
py: Python<'p>, py: Python<'py>,
year: i32, year: i32,
month: u8, month: u8,
day: u8, day: u8,
@ -313,12 +411,12 @@ impl PyDateTime {
minute: u8, minute: u8,
second: u8, second: u8,
microsecond: u32, microsecond: u32,
tzinfo: Option<&PyTzInfo>, tzinfo: Option<&Bound<'py, PyTzInfo>>,
fold: bool, fold: bool,
) -> PyResult<&'p PyDateTime> { ) -> PyResult<Bound<'py, PyDateTime>> {
let api = ensure_datetime_api(py)?; let api = ensure_datetime_api(py)?;
unsafe { unsafe {
let ptr = (api.DateTime_FromDateAndTimeAndFold)( (api.DateTime_FromDateAndTimeAndFold)(
year, year,
c_int::from(month), c_int::from(month),
c_int::from(day), c_int::from(day),
@ -329,27 +427,46 @@ impl PyDateTime {
opt_to_pyobj(tzinfo), opt_to_pyobj(tzinfo),
c_int::from(fold), c_int::from(fold),
api.DateTimeType, 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 /// Construct a `datetime` object from a POSIX timestamp
/// ///
/// This is equivalent to `datetime.datetime.fromtimestamp` /// This is equivalent to `datetime.datetime.fromtimestamp`
pub fn from_timestamp<'p>( pub fn from_timestamp_bound<'py>(
py: Python<'p>, py: Python<'py>,
timestamp: f64, timestamp: f64,
tzinfo: Option<&PyTzInfo>, tzinfo: Option<&Bound<'py, PyTzInfo>>,
) -> PyResult<&'p PyDateTime> { ) -> PyResult<Bound<'py, PyDateTime>> {
let args: Py<PyTuple> = (timestamp, tzinfo).into_py(py); let args = IntoPy::<Py<PyTuple>>::into_py((timestamp, tzinfo), py).into_bound(py);
// safety ensure API is loaded // safety ensure API is loaded
let _api = ensure_datetime_api(py)?; let _api = ensure_datetime_api(py)?;
unsafe { unsafe {
let ptr = PyDateTime_FromTimestamp(args.as_ptr()); PyDateTime_FromTimestamp(args.as_ptr())
py.from_owned_ptr_or_err(ptr) .assume_owned_or_err(py)
.downcast_into_unchecked()
} }
} }
} }
@ -463,42 +580,99 @@ pyobject_native_type!(
); );
impl PyTime { impl PyTime {
/// Creates a new `datetime.time` object. /// Deprecated form of [`PyTime::new_bound`].
pub fn new<'p>( #[cfg_attr(
py: Python<'p>, 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, hour: u8,
minute: u8, minute: u8,
second: u8, second: u8,
microsecond: u32, microsecond: u32,
tzinfo: Option<&PyTzInfo>, tzinfo: Option<&'py PyTzInfo>,
) -> PyResult<&'p PyTime> { ) -> 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<Bound<'py, PyTime>> {
let api = ensure_datetime_api(py)?; let api = ensure_datetime_api(py)?;
unsafe { unsafe {
let ptr = (api.Time_FromTime)( (api.Time_FromTime)(
c_int::from(hour), c_int::from(hour),
c_int::from(minute), c_int::from(minute),
c_int::from(second), c_int::from(second),
microsecond as c_int, microsecond as c_int,
opt_to_pyobj(tzinfo), opt_to_pyobj(tzinfo),
api.TimeType, 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`]. /// Deprecated form of [`PyTime::new_bound_with_fold`].
pub fn new_with_fold<'p>( #[cfg_attr(
py: Python<'p>, 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, hour: u8,
minute: u8, minute: u8,
second: u8, second: u8,
microsecond: u32, microsecond: u32,
tzinfo: Option<&PyTzInfo>, tzinfo: Option<&'py PyTzInfo>,
fold: bool, 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<Bound<'py, PyTime>> {
let api = ensure_datetime_api(py)?; let api = ensure_datetime_api(py)?;
unsafe { unsafe {
let ptr = (api.Time_FromTimeAndFold)( (api.Time_FromTimeAndFold)(
c_int::from(hour), c_int::from(hour),
c_int::from(minute), c_int::from(minute),
c_int::from(second), c_int::from(second),
@ -506,8 +680,9 @@ impl PyTime {
opt_to_pyobj(tzinfo), opt_to_pyobj(tzinfo),
fold as c_int, fold as c_int,
api.TimeType, 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 #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 { 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 /// Equivalent to `datetime.timezone` constructor
/// ///
/// Only used internally /// Only used internally
#[cfg(feature = "chrono")] #[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<Bound<'py, PyTzInfo>> {
let py = offset.py();
let api = ensure_datetime_api(py)?; let api = ensure_datetime_api(py)?;
unsafe { unsafe {
let ptr = (api.TimeZone_FromTimeZone)(offset.as_ptr(), ptr::null_mut()); (api.TimeZone_FromTimeZone)(offset.as_ptr(), ptr::null_mut())
py.from_owned_ptr_or_err(ptr) .assume_owned_or_err(py)
.downcast_into_unchecked()
} }
} }
@ -625,7 +825,14 @@ pyobject_native_type!(
); );
impl PyDelta { 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( pub fn new(
py: Python<'_>, py: Python<'_>,
days: i32, days: i32,
@ -633,16 +840,28 @@ impl PyDelta {
microseconds: i32, microseconds: i32,
normalize: bool, normalize: bool,
) -> PyResult<&PyDelta> { ) -> 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<Bound<'_, PyDelta>> {
let api = ensure_datetime_api(py)?; let api = ensure_datetime_api(py)?;
unsafe { unsafe {
let ptr = (api.Delta_FromDelta)( (api.Delta_FromDelta)(
days as c_int, days as c_int,
seconds as c_int, seconds as c_int,
microseconds as c_int, microseconds as c_int,
normalize as c_int, normalize as c_int,
api.DeltaType, 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 // Utility function which returns a borrowed reference to either
// the underlying tzinfo or None. // 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 { match opt {
Some(tzi) => tzi.as_ptr(), Some(tzi) => tzi.as_ptr(),
None => unsafe { ffi::Py_None() }, None => unsafe { ffi::Py_None() },
@ -685,6 +904,7 @@ fn opt_to_pyobj(opt: Option<&PyTzInfo>) -> *mut ffi::PyObject {
} }
#[cfg(test)] #[cfg(test)]
#[cfg_attr(not(feature = "gil-refs"), allow(deprecated))]
mod tests { mod tests {
use super::*; use super::*;
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
@ -768,7 +988,7 @@ mod tests {
fn test_timezone_from_offset() { fn test_timezone_from_offset() {
Python::with_gil(|py| { Python::with_gil(|py| {
assert!( 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() .unwrap()
.call_method1("utcoffset", ((),)) .call_method1("utcoffset", ((),))
.unwrap() .unwrap()
@ -779,7 +999,7 @@ mod tests {
); );
assert!( 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() .unwrap()
.call_method1("utcoffset", ((),)) .call_method1("utcoffset", ((),))
.unwrap() .unwrap()
@ -789,7 +1009,7 @@ mod tests {
.unwrap() .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();
}) })
} }
} }

View File

@ -8,10 +8,13 @@ pub use self::capsule::PyCapsule;
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
pub use self::code::PyCode; pub use self::code::PyCode;
pub use self::complex::PyComplex; pub use self::complex::PyComplex;
#[allow(deprecated)]
#[cfg(not(Py_LIMITED_API))]
pub use self::datetime::timezone_utc;
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
pub use self::datetime::{ pub use self::datetime::{
timezone_utc, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, timezone_utc_bound, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime,
PyTzInfo, PyTzInfoAccess, PyTimeAccess, PyTzInfo, PyTzInfoAccess,
}; };
pub use self::dict::{IntoPyDict, PyDict}; pub use self::dict::{IntoPyDict, PyDict};
#[cfg(not(PyPy))] #[cfg(not(PyPy))]

View File

@ -1,7 +1,7 @@
#![cfg(not(Py_LIMITED_API))] #![cfg(not(Py_LIMITED_API))]
use pyo3::prelude::*; 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; use pyo3_ffi::PyDateTime_IMPORT;
fn _get_subclasses<'py>( fn _get_subclasses<'py>(
@ -118,9 +118,9 @@ fn test_datetime_utc() {
use pyo3::types::PyDateTime; use pyo3::types::PyDateTime;
Python::with_gil(|py| { 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); let locals = [("dt", dt)].into_py_dict_bound(py);
@ -155,7 +155,7 @@ fn test_pydate_out_of_bounds() {
Python::with_gil(|py| { Python::with_gil(|py| {
for val in INVALID_DATES { for val in INVALID_DATES {
let (year, month, day) = val; 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(); dt.unwrap_err();
} }
}); });
@ -168,7 +168,7 @@ fn test_pytime_out_of_bounds() {
Python::with_gil(|py| { Python::with_gil(|py| {
for val in INVALID_TIMES { for val in INVALID_TIMES {
let (hour, minute, second, microsecond) = val; 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(); dt.unwrap_err();
} }
}); });
@ -192,7 +192,7 @@ fn test_pydatetime_out_of_bounds() {
let (date, time) = val; let (date, time) = val;
let (year, month, day) = date; let (year, month, day) = date;
let (hour, minute, second, microsecond) = time; let (hour, minute, second, microsecond) = time;
let dt = PyDateTime::new( let dt = PyDateTime::new_bound(
py, py,
*year, *year,
*month, *month,

View File

@ -21,6 +21,6 @@ fn test_bad_datetime_module_panic() {
.unwrap(); .unwrap();
// This should panic because the "datetime" module is empty // This should panic because the "datetime" module is empty
PyDate::new(py, 2018, 1, 1).unwrap(); PyDate::new_bound(py, 2018, 1, 1).unwrap();
}); });
} }