Skip to content

Commit

Permalink
Deprecate allowing datetime.datetime instances as values for Date tra…
Browse files Browse the repository at this point in the history
…its (#1441)

* Deprecate allowing datetime.datetime instances as values for Date traits

* Fix misspelled Sphinx class role

* Update traits/trait_types.py

* Fix long line
  • Loading branch information
mdickinson authored Mar 15, 2021
1 parent 268b4d9 commit 2b62cd9
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 16 deletions.
30 changes: 21 additions & 9 deletions traits/tests/test_date.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,33 +73,45 @@ def test_assign_non_date(self):
message = str(exception_context.exception)
self.assertIn("must be a date or None, but", message)

def test_assign_datetime(self):
# By default, datetime instances are permitted.
test_datetime = datetime.datetime(1975, 2, 13)
obj = HasDateTraits()
obj.simple_date = test_datetime
self.assertEqual(obj.simple_date, test_datetime)

def test_assign_none(self):
obj = HasDateTraits(simple_date=UNIX_EPOCH)
self.assertIsNotNone(obj.simple_date)
obj.simple_date = None
self.assertIsNone(obj.simple_date)

def test_allow_datetime_false(self):
def test_assign_datetime_with_allow_datetime_false(self):
test_datetime = datetime.datetime(1975, 2, 13)
obj = HasDateTraits()
with self.assertRaises(TraitError) as exception_context:
obj.datetime_prohibited = test_datetime
message = str(exception_context.exception)
self.assertIn("must be a non-datetime date or None, but", message)

def test_allow_datetime_true(self):
def test_assign_datetime_with_allow_datetime_true(self):
test_datetime = datetime.datetime(1975, 2, 13)
obj = HasDateTraits()
obj.datetime_allowed = test_datetime
self.assertEqual(obj.datetime_allowed, test_datetime)

def test_assign_datetime_with_allow_datetime_not_given(self):
# For traits where "allow_datetime" is not specified, a
# DeprecationWarning should be issued on assignment of datetime.
test_datetime = datetime.datetime(1975, 2, 13)
obj = HasDateTraits()
with self.assertWarns(DeprecationWarning) as warnings_cm:
# Note: the warning depends on the type, not the value; this case
# should warn even though the time component of the datetime is
# zero.
obj.simple_date = test_datetime
self.assertEqual(obj.simple_date, test_datetime)

_, _, this_module = __name__.rpartition(".")
self.assertIn(this_module, warnings_cm.filename)
self.assertIn(
"datetime instances will no longer be accepted",
str(warnings_cm.warning),
)

def test_allow_none_false(self):
obj = HasDateTraits(none_prohibited=UNIX_EPOCH)
with self.assertRaises(TraitError) as exception_context:
Expand Down
45 changes: 38 additions & 7 deletions traits/trait_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import sys
from types import FunctionType, MethodType, ModuleType
import uuid
import warnings

from .constants import DefaultValue, TraitKind, ValidateTrait
from .trait_base import (
Expand Down Expand Up @@ -4268,14 +4269,23 @@ class Date(TraitType):
by default instances of :class:`datetime.datetime` are also permitted.
Use ``Date(allow_datetime=False)`` to exclude this possibility.
.. deprecated:: 6.3.0
In the future, :class:`datetime.datetime` instances will not be valid
values for this trait type unless "allow_datetime=True" is explicitly
given.
Parameters
----------
default_value : datetime.date, optional
The default value for this trait. If no default is provided, the
default is ``None``.
allow_datetime : bool, optional
If ``False``, instances of ``datetime.datetime`` are not valid
values for this Trait. The default is ``True``.
values for this Trait. If ``True``, ``datetime.datetime`` instances
are explicitly permitted. If this argument is not given,
``datetime.datetime`` instances will be accepted, but a
``DeprecationWarning`` will be issued; in some future version of
Traits, ``datetime.datetime`` instances will not be permitted.
allow_none : bool, optional
If ``False``, it's not permitted to assign ``None`` to this trait.
The default is ``True``.
Expand All @@ -4287,7 +4297,7 @@ def __init__(
self,
default_value=None,
*,
allow_datetime=True,
allow_datetime=None,
allow_none=True,
**metadata,
):
Expand All @@ -4298,19 +4308,40 @@ def __init__(
def validate(self, object, name, value):
""" Check that the given value is valid date for this trait.
"""
if value is None and self.allow_none:
return value
if isinstance(value, datetime.date):
if self.allow_datetime or not isinstance(value, datetime.datetime):
if value is None:
if self.allow_none:
return value

elif isinstance(value, datetime.datetime):
if self.allow_datetime:
return value
elif self.allow_datetime is None:
warnings.warn(
(
"In the future, datetime.datetime instances will no "
"longer be accepted by this trait type. To accept "
"datetimes and silence this warning, use "
"Date(allow_datetime=True) or "
"Union(Datetime(), Date())."
),
DeprecationWarning,
stacklevel=2,
)
return value

elif isinstance(value, datetime.date):
return value

self.error(object, name, value)

def info(self):
"""
Return text description of this trait.
"""
datetime_qualifier = "" if self.allow_datetime else " non-datetime"
if self.allow_datetime or self.allow_datetime is None:
datetime_qualifier = ""
else:
datetime_qualifier = " non-datetime"
none_qualifier = " or None" if self.allow_none else ""
return f"a{datetime_qualifier} date{none_qualifier}"

Expand Down

0 comments on commit 2b62cd9

Please sign in to comment.