Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate allowing datetime.datetime instances as values for Date traits #1441

Merged
merged 4 commits into from
Mar 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is redundant with the new test_assign_datetime_with_allow_datetime_not_given test

# 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 @@ -4261,14 +4262,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 @@ -4280,7 +4290,7 @@ def __init__(
self,
default_value=None,
*,
allow_datetime=True,
allow_datetime=None,
allow_none=True,
**metadata,
):
Expand All @@ -4291,19 +4301,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