From d7b90c1b3a8cd16c6f38fe07ec1b36ad4b850fbb Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Fri, 6 Apr 2018 13:39:45 -0400 Subject: [PATCH] Add datetime.datetime and switch to PyResult> --- src/ffi3/datetime.rs | 27 ++++++++--- src/objects/datetime.rs | 50 +++++++++++++++++---- src/objects/mod.rs | 2 +- tests/rustapi_module/src/lib.rs | 18 ++++++-- tests/rustapi_module/tests/test_datetime.py | 23 +++++++++- 5 files changed, 101 insertions(+), 19 deletions(-) diff --git a/src/ffi3/datetime.rs b/src/ffi3/datetime.rs index 831aa924..6d85c8c1 100644 --- a/src/ffi3/datetime.rs +++ b/src/ffi3/datetime.rs @@ -5,12 +5,12 @@ use ffi3::object::*; use ffi3::pycapsule::PyCapsule_Import; #[cfg_attr(windows, link(name="pythonXY"))] extern "C" { - pub static mut PyDateTime_Date: PyTypeObject; - pub static mut PyDateTime_Time: PyTypeObject; - pub static mut PyDateTime_DateTime: PyTypeObject; + pub static mut PyDateTime_DateType: PyTypeObject; + pub static mut PyDateTime_TimeType: PyTypeObject; + pub static mut PyDateTime_DateTimeType: PyTypeObject; - pub static mut PyDateTime_Delta: PyTypeObject; - pub static mut PyDateTime_TZInfo: PyTypeObject; + pub static mut PyDateTime_DeltaType: PyTypeObject; + pub static mut PyDateTime_TZInfoType: PyTypeObject; } @@ -101,6 +101,11 @@ pub struct PyDateTime_CAPI { unsafe impl Sync for PyDateTime_CAPI {} +lazy_static! { + pub static ref PyDateTimeAPI: PyDateTime_CAPI = unsafe { PyDateTime_IMPORT() }; +} + + #[inline(always)] pub unsafe fn PyDateTime_IMPORT() -> PyDateTime_CAPI { // PyDateTime_CAPSULE_NAME is a macro in C @@ -113,5 +118,15 @@ pub unsafe fn PyDateTime_IMPORT() -> PyDateTime_CAPI { #[inline(always)] pub unsafe fn PyDate_Check(op: *mut PyObject) -> c_int { - PyObject_TypeCheck(op, &mut PyDateTime_Date) as c_int + PyObject_TypeCheck(op, PyDateTimeAPI.DateType) as c_int +} + +#[inline(always)] +pub unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, PyDateTimeAPI.DateTimeType) as c_int +} + +#[inline(always)] +pub unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, PyDateTimeAPI.TZInfoType) as c_int } diff --git a/src/objects/datetime.rs b/src/objects/datetime.rs index ec0e166c..724c796c 100644 --- a/src/objects/datetime.rs +++ b/src/objects/datetime.rs @@ -1,27 +1,61 @@ +use err::PyResult; use object::PyObject; use std::os::raw::c_int; -use ffi::{PyDateTime_CAPI, PyDateTime_IMPORT}; +use ffi::{PyDateTimeAPI}; use python::{Python, ToPyPointer}; use instance::Py; + + +// datetime.date bindings pub struct PyDate(PyObject); pyobject_convert!(PyDate); -pyobject_nativetype!(PyDate, PyDateTime_Date, PyDate_Check); - -lazy_static! { - static ref PyDateTimeAPI: PyDateTime_CAPI = unsafe { PyDateTime_IMPORT() }; -} +pyobject_nativetype!(PyDate, PyDateTime_DateType, PyDate_Check); impl PyDate { - pub fn new(py: Python, year: u32, month: u32, day: u32) -> Py { + pub fn new(py: Python, year: u32, month: u32, day: u32) -> PyResult> { let y = year as c_int; let m = month as c_int; let d = day as c_int; unsafe { let ptr = PyDateTimeAPI.Date_FromDate.unwrap()(y, m, d, PyDateTimeAPI.DateType); - Py::from_owned_ptr_or_panic(ptr) + Py::from_owned_ptr_or_err(py, ptr) } } } + +// datetime.datetime bindings +pub struct PyDateTime(PyObject); +pyobject_convert!(PyDateTime); +pyobject_nativetype!(PyDateTime, PyDateTime_DateTimeType, PyDateTime_Check); + + +impl PyDateTime { + pub fn new(py: Python, year: u32, month: u32, day: u32, + hour: u32, minute: u32, second: u32, microsecond: u32, + tzinfo: &PyObject) -> PyResult> { + let y = year as c_int; + let mo = month as c_int; + let d = day as c_int; + let h = hour as c_int; + let mi = minute as c_int; + let s = second as c_int; + let u = microsecond as c_int; + + unsafe { + let ptr = PyDateTimeAPI.DateTime_FromDateAndTime.unwrap()( + y, mo, d, h, mi, s, u, tzinfo.as_ptr(), + PyDateTimeAPI.DateTimeType + ); + Py::from_owned_ptr_or_err(py, ptr) + } + } +} + +// datetime.tzinfo bindings +pub struct PyTzInfo(PyObject); +pyobject_convert!(PyTzInfo); +pyobject_nativetype!(PyTzInfo, PyDateTime_TZInfoType, PyTZInfo_Check); + diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 62b098d6..36c6ff1d 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -5,7 +5,7 @@ mod exc_impl; pub use self::boolobject::PyBool; pub use self::bytearray::PyByteArray; -pub use self::datetime::{PyDate}; +pub use self::datetime::{PyDate,PyDateTime,PyTzInfo}; pub use self::dict::PyDict; pub use self::floatob::PyFloat; pub use self::iterator::PyIterator; diff --git a/tests/rustapi_module/src/lib.rs b/tests/rustapi_module/src/lib.rs index 4781f13a..862ab598 100644 --- a/tests/rustapi_module/src/lib.rs +++ b/tests/rustapi_module/src/lib.rs @@ -1,15 +1,27 @@ #![feature(proc_macro, specialization)] extern crate pyo3; - use pyo3::{py, Py, Python, PyModule, PyResult}; -use pyo3::prelude::PyDate; +use pyo3::{ToPyObject}; +use pyo3::prelude::{PyObject}; +use pyo3::prelude::{PyDate, PyDateTime, PyTzInfo}; #[py::modinit(datetime)] fn init_mod(py: Python, m: &PyModule) -> PyResult<()> { #[pyfn(m, "make_date")] fn make_date(py: Python, year: u32, month: u32, day: u32) -> PyResult> { - Ok(PyDate::new(py, year, month, day)) + PyDate::new(py, year, month, day) + } + + #[pyfn(m, "make_datetime")] + fn make_datetime(py: Python, year: u32, month: u32, day: u32, + hour: u32, minute: u32, second: u32, microsecond: u32, + tzinfo: Option<&PyTzInfo>) -> PyResult> { + let tzi : PyObject = match tzinfo { + Some(t) => t.to_object(py), + None => py.None(), + }; + PyDateTime::new(py, year, month, day, hour, minute, second, microsecond, &tzi) } Ok(()) diff --git a/tests/rustapi_module/tests/test_datetime.py b/tests/rustapi_module/tests/test_datetime.py index 313beb38..cc01331c 100644 --- a/tests/rustapi_module/tests/test_datetime.py +++ b/tests/rustapi_module/tests/test_datetime.py @@ -9,6 +9,27 @@ def test_date(): assert rdt.make_date(2017, 9, 1) == pdt.date(2017, 9, 1) -def test_date_fails(): +def test_invalid_date_fails(): with pytest.raises(ValueError): rdt.make_date(2017, 2, 30) + + +@pytest.mark.parametrize('args, kwargs', [ + ((2017, 9, 1, 12, 45, 30, 0), {}), + ((2017, 9, 1, 12, 45, 30, 0), {'tzinfo': pdt.timezone.utc}), +]) +def test_datetime(args, kwargs): + act = rdt.make_datetime(*args, **kwargs) + exp = pdt.datetime(*args, **kwargs) + + assert act == exp + + +def test_invalid_datetime_fails(): + with pytest.raises(ValueError): + rdt.make_datetime(2011, 1, 42, 0, 0, 0, 0) + + +def test_datetime_typeerror(): + with pytest.raises(TypeError): + rdt.make_datetime('2011', 1, 1, 0, 0, 0, 0)