2020-12-15 15:04:40 +00:00
|
|
|
#![cfg(not(Py_LIMITED_API))]
|
|
|
|
|
2018-08-21 18:14:01 +00:00
|
|
|
use pyo3::prelude::*;
|
2024-02-12 20:49:58 +00:00
|
|
|
use pyo3::types::{timezone_utc_bound, IntoPyDict, PyDate, PyDateTime, PyTime};
|
2022-01-25 13:46:27 +00:00
|
|
|
use pyo3_ffi::PyDateTime_IMPORT;
|
2018-08-09 18:17:34 +00:00
|
|
|
|
2024-02-05 18:13:02 +00:00
|
|
|
fn _get_subclasses<'py>(
|
|
|
|
py: Python<'py>,
|
2018-08-09 20:29:59 +00:00
|
|
|
py_type: &str,
|
|
|
|
args: &str,
|
2024-02-05 18:13:02 +00:00
|
|
|
) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>)> {
|
2018-08-09 18:17:34 +00:00
|
|
|
// Import the class from Python and create some subclasses
|
2024-02-14 00:24:37 +00:00
|
|
|
let datetime = py.import_bound("datetime")?;
|
2018-08-09 18:17:34 +00:00
|
|
|
|
2024-02-10 12:59:55 +00:00
|
|
|
let locals = [(py_type, datetime.getattr(py_type)?)].into_py_dict_bound(py);
|
2018-08-09 18:17:34 +00:00
|
|
|
|
2018-08-09 20:29:59 +00:00
|
|
|
let make_subclass_py = format!("class Subklass({}):\n pass", py_type);
|
2018-08-09 18:17:34 +00:00
|
|
|
|
2018-08-09 20:29:59 +00:00
|
|
|
let make_sub_subclass_py = "class SubSubklass(Subklass):\n pass";
|
2018-08-09 18:17:34 +00:00
|
|
|
|
2024-02-05 18:44:00 +00:00
|
|
|
py.run_bound(&make_subclass_py, None, Some(&locals))?;
|
|
|
|
py.run_bound(make_sub_subclass_py, None, Some(&locals))?;
|
2018-08-09 18:17:34 +00:00
|
|
|
|
|
|
|
// Construct an instance of the base class
|
2024-02-05 18:13:02 +00:00
|
|
|
let obj = py.eval_bound(&format!("{}({})", py_type, args), None, Some(&locals))?;
|
2018-08-09 18:17:34 +00:00
|
|
|
|
|
|
|
// Construct an instance of the subclass
|
2024-02-05 18:13:02 +00:00
|
|
|
let sub_obj = py.eval_bound(&format!("Subklass({})", args), None, Some(&locals))?;
|
2018-08-09 18:17:34 +00:00
|
|
|
|
|
|
|
// Construct an instance of the sub-subclass
|
2024-02-05 18:13:02 +00:00
|
|
|
let sub_sub_obj = py.eval_bound(&format!("SubSubklass({})", args), None, Some(&locals))?;
|
2018-08-09 18:17:34 +00:00
|
|
|
|
2018-08-20 19:11:54 +00:00
|
|
|
Ok((obj, sub_obj, sub_sub_obj))
|
2018-08-09 18:17:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! assert_check_exact {
|
2021-12-03 00:03:32 +00:00
|
|
|
($check_func:ident, $check_func_exact:ident, $obj: expr) => {
|
2018-08-09 18:17:34 +00:00
|
|
|
unsafe {
|
2023-07-30 20:58:21 +00:00
|
|
|
use pyo3::ffi::*;
|
2018-08-09 18:17:34 +00:00
|
|
|
assert!($check_func(($obj).as_ptr()) != 0);
|
2021-12-03 00:03:32 +00:00
|
|
|
assert!($check_func_exact(($obj).as_ptr()) != 0);
|
2018-08-09 18:17:34 +00:00
|
|
|
}
|
2018-08-09 20:29:59 +00:00
|
|
|
};
|
2018-08-09 18:17:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! assert_check_only {
|
2021-12-03 00:03:32 +00:00
|
|
|
($check_func:ident, $check_func_exact:ident, $obj: expr) => {
|
2018-08-09 18:17:34 +00:00
|
|
|
unsafe {
|
2023-07-30 20:58:21 +00:00
|
|
|
use pyo3::ffi::*;
|
2018-08-09 18:17:34 +00:00
|
|
|
assert!($check_func(($obj).as_ptr()) != 0);
|
2021-12-03 00:03:32 +00:00
|
|
|
assert!($check_func_exact(($obj).as_ptr()) == 0);
|
2018-08-09 18:17:34 +00:00
|
|
|
}
|
2018-08-09 20:29:59 +00:00
|
|
|
};
|
2018-08-09 18:17:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_date_check() {
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
|
|
|
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);
|
2023-01-15 10:06:45 +00:00
|
|
|
assert!(obj.is_instance_of::<PyDate>());
|
|
|
|
assert!(!obj.is_instance_of::<PyTime>());
|
|
|
|
assert!(!obj.is_instance_of::<PyDateTime>());
|
2022-07-19 17:34:23 +00:00
|
|
|
});
|
2018-08-09 18:17:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_time_check() {
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
|
|
|
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);
|
|
|
|
assert_check_only!(PyTime_Check, PyTime_CheckExact, sub_sub_obj);
|
2023-01-15 10:06:45 +00:00
|
|
|
assert!(!obj.is_instance_of::<PyDate>());
|
|
|
|
assert!(obj.is_instance_of::<PyTime>());
|
|
|
|
assert!(!obj.is_instance_of::<PyDateTime>());
|
2022-07-19 17:34:23 +00:00
|
|
|
});
|
2018-08-09 18:17:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_datetime_check() {
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
|
|
|
let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "datetime", "2018, 1, 1, 13, 30, 15")
|
2023-07-21 13:30:02 +00:00
|
|
|
.map_err(|e| e.display(py))
|
2022-07-19 17:34:23 +00:00
|
|
|
.unwrap();
|
|
|
|
unsafe { PyDateTime_IMPORT() }
|
|
|
|
|
|
|
|
assert_check_only!(PyDate_Check, PyDate_CheckExact, obj);
|
|
|
|
assert_check_exact!(PyDateTime_Check, PyDateTime_CheckExact, obj);
|
|
|
|
assert_check_only!(PyDateTime_Check, PyDateTime_CheckExact, sub_obj);
|
|
|
|
assert_check_only!(PyDateTime_Check, PyDateTime_CheckExact, sub_sub_obj);
|
2023-01-15 10:06:45 +00:00
|
|
|
assert!(obj.is_instance_of::<PyDate>());
|
|
|
|
assert!(!obj.is_instance_of::<PyTime>());
|
|
|
|
assert!(obj.is_instance_of::<PyDateTime>());
|
2022-07-19 17:34:23 +00:00
|
|
|
});
|
2018-08-09 18:17:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_delta_check() {
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
|
|
|
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);
|
|
|
|
assert_check_only!(PyDelta_Check, PyDelta_CheckExact, sub_sub_obj);
|
|
|
|
});
|
2018-08-09 18:17:34 +00:00
|
|
|
}
|
2018-08-20 17:31:39 +00:00
|
|
|
|
2018-08-20 17:52:19 +00:00
|
|
|
#[test]
|
|
|
|
fn test_datetime_utc() {
|
2019-08-17 12:10:36 +00:00
|
|
|
use assert_approx_eq::assert_approx_eq;
|
2019-02-23 17:42:40 +00:00
|
|
|
use pyo3::types::PyDateTime;
|
|
|
|
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
2024-02-12 20:49:58 +00:00
|
|
|
let utc = timezone_utc_bound(py);
|
2018-08-20 17:52:19 +00:00
|
|
|
|
2024-02-12 20:49:58 +00:00
|
|
|
let dt = PyDateTime::new_bound(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
|
2018-08-20 17:52:19 +00:00
|
|
|
|
2024-02-10 12:59:55 +00:00
|
|
|
let locals = [("dt", dt)].into_py_dict_bound(py);
|
2018-08-20 17:52:19 +00:00
|
|
|
|
2022-07-19 17:34:23 +00:00
|
|
|
let offset: f32 = py
|
2024-02-05 18:13:02 +00:00
|
|
|
.eval_bound("dt.utcoffset().total_seconds()", None, Some(&locals))
|
2022-07-19 17:34:23 +00:00
|
|
|
.unwrap()
|
|
|
|
.extract()
|
|
|
|
.unwrap();
|
|
|
|
assert_approx_eq!(offset, 0f32);
|
|
|
|
});
|
2018-08-20 17:52:19 +00:00
|
|
|
}
|
|
|
|
|
2019-08-17 12:10:36 +00:00
|
|
|
static INVALID_DATES: &[(i32, u8, u8)] = &[
|
2018-08-21 18:14:01 +00:00
|
|
|
(-1, 1, 1),
|
|
|
|
(0, 1, 1),
|
|
|
|
(10000, 1, 1),
|
|
|
|
(2 << 30, 1, 1),
|
|
|
|
(2018, 0, 1),
|
|
|
|
(2018, 13, 1),
|
|
|
|
(2018, 1, 0),
|
|
|
|
(2017, 2, 29),
|
|
|
|
(2018, 1, 32),
|
2018-08-21 17:43:32 +00:00
|
|
|
];
|
|
|
|
|
2019-08-17 12:10:36 +00:00
|
|
|
static INVALID_TIMES: &[(u8, u8, u8, u32)] =
|
2018-08-21 18:14:01 +00:00
|
|
|
&[(25, 0, 0, 0), (255, 0, 0, 0), (0, 60, 0, 0), (0, 0, 61, 0)];
|
2018-08-21 17:43:32 +00:00
|
|
|
|
2018-08-20 17:31:39 +00:00
|
|
|
#[test]
|
|
|
|
fn test_pydate_out_of_bounds() {
|
2019-02-23 17:42:40 +00:00
|
|
|
use pyo3::types::PyDate;
|
|
|
|
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
|
|
|
for val in INVALID_DATES {
|
|
|
|
let (year, month, day) = val;
|
2024-02-12 20:49:58 +00:00
|
|
|
let dt = PyDate::new_bound(py, *year, *month, *day);
|
2022-07-19 17:34:23 +00:00
|
|
|
dt.unwrap_err();
|
|
|
|
}
|
|
|
|
});
|
2018-08-20 17:31:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_pytime_out_of_bounds() {
|
2019-02-23 17:42:40 +00:00
|
|
|
use pyo3::types::PyTime;
|
|
|
|
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
|
|
|
for val in INVALID_TIMES {
|
|
|
|
let (hour, minute, second, microsecond) = val;
|
2024-02-12 20:49:58 +00:00
|
|
|
let dt = PyTime::new_bound(py, *hour, *minute, *second, *microsecond, None);
|
2022-07-19 17:34:23 +00:00
|
|
|
dt.unwrap_err();
|
|
|
|
}
|
|
|
|
});
|
2018-08-20 17:31:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_pydatetime_out_of_bounds() {
|
2019-02-23 17:42:40 +00:00
|
|
|
use pyo3::types::PyDateTime;
|
|
|
|
use std::iter;
|
|
|
|
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
|
|
|
let valid_time = (0, 0, 0, 0);
|
|
|
|
let valid_date = (2018, 1, 1);
|
|
|
|
|
|
|
|
let invalid_dates = INVALID_DATES.iter().zip(iter::repeat(&valid_time));
|
|
|
|
let invalid_times = iter::repeat(&valid_date).zip(INVALID_TIMES.iter());
|
|
|
|
|
|
|
|
let vals = invalid_dates.chain(invalid_times);
|
|
|
|
|
|
|
|
for val in vals {
|
|
|
|
let (date, time) = val;
|
|
|
|
let (year, month, day) = date;
|
|
|
|
let (hour, minute, second, microsecond) = time;
|
2024-02-12 20:49:58 +00:00
|
|
|
let dt = PyDateTime::new_bound(
|
2022-07-19 17:34:23 +00:00
|
|
|
py,
|
|
|
|
*year,
|
|
|
|
*month,
|
|
|
|
*day,
|
|
|
|
*hour,
|
|
|
|
*minute,
|
|
|
|
*second,
|
|
|
|
*microsecond,
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
dt.unwrap_err();
|
|
|
|
}
|
|
|
|
});
|
2018-08-20 17:31:39 +00:00
|
|
|
}
|