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

Fix allow_none to work correctly on BaseInstance objects #1433

Merged
merged 8 commits into from
Feb 9, 2021
Merged
Show file tree
Hide file tree
Changes from 7 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
5 changes: 1 addition & 4 deletions traits/tests/test_date.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,8 @@ def test_assign_datetime(self):
self.assertEqual(obj.simple_date, test_datetime)

def test_assign_none(self):
# This is a test for the current behaviour. There may be an argument
# for optionally disallowing None. Note that specifying
# allow_none=False in the trait definition does not work as expected.
# (Ref: enthought/traits#495)
obj = HasDateTraits(simple_date=UNIX_EPOCH)
self.assertIsNotNone(obj.simple_date)
obj.simple_date = None
self.assertIsNone(obj.simple_date)

Expand Down
100 changes: 100 additions & 0 deletions traits/tests/test_datetime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!

"""
Tests for the Datetime trait type.
"""

import datetime
import unittest

from traits.testing.optional_dependencies import requires_traitsui, traitsui

from traits.api import Datetime, HasStrictTraits, TraitError


#: Unix epoch
UNIX_EPOCH = datetime.datetime(1970, 1, 1, 0, 0)

#: Windows NT epoch
NT_EPOCH = datetime.datetime(1600, 1, 1, 0, 0)


class HasDatetimeTraits(HasStrictTraits):
#: Simple case - no default, no parameters, no metadata
simple_datetime = Datetime()

#: Datetime with default
epoch = Datetime(UNIX_EPOCH)

#: Datetime with default provided via keyword.
alternative_epoch = Datetime(default_value=NT_EPOCH)

#: None prohibited
none_prohibited = Datetime(allow_none=False)

#: None allowed
none_allowed = Datetime(allow_none=True)


class TestDatetime(unittest.TestCase):
def test_default(self):
obj = HasDatetimeTraits()
self.assertEqual(obj.simple_datetime, None)
self.assertEqual(obj.epoch, UNIX_EPOCH)
self.assertEqual(obj.alternative_epoch, NT_EPOCH)

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

def test_assign_non_datetime(self):
obj = HasDatetimeTraits()
with self.assertRaises(TraitError) as exception_context:
obj.simple_datetime = "2021-02-05 12:00:00"
message = str(exception_context.exception)
self.assertIn("must be a datetime or None, but", message)

def test_assign_date(self):
obj = HasDatetimeTraits()
with self.assertRaises(TraitError) as exception_context:
obj.simple_datetime = datetime.date(1975, 2, 13)
message = str(exception_context.exception)
self.assertIn("must be a datetime or None, but", message)
self.assertIsNone(obj.simple_datetime)

def test_assign_none(self):
obj = HasDatetimeTraits(simple_datetime=UNIX_EPOCH)
self.assertIsNotNone(obj.simple_datetime)
obj.simple_datetime = None
self.assertIsNone(obj.simple_datetime)

def test_allow_none_false(self):
obj = HasDatetimeTraits(none_prohibited=UNIX_EPOCH)
with self.assertRaises(TraitError) as exception_context:
obj.none_prohibited = None
message = str(exception_context.exception)
self.assertIn("must be a datetime, but", message)

def test_allow_none_true(self):
obj = HasDatetimeTraits(none_allowed=UNIX_EPOCH)
self.assertIsNotNone(obj.none_allowed)
obj.none_allowed = None
self.assertIsNone(obj.none_allowed)

@requires_traitsui
def test_get_editor(self):
obj = HasDatetimeTraits()
trait = obj.base_trait("epoch")
editor_factory = trait.get_editor()
self.assertIsInstance(editor_factory, traitsui.api.DatetimeEditor)
73 changes: 73 additions & 0 deletions traits/tests/test_instance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!

"""
Tests for the Instance and BaseInstance trait types.
"""

import unittest

from traits.api import BaseInstance, HasStrictTraits, Instance, TraitError


#: Define a new "trait type" using BaseInstance. This is similar to the
#: way that Datetime and Time are defined.
Slice = BaseInstance(slice)


class HasSlices(HasStrictTraits):
my_slice = Instance(slice)

also_my_slice = Slice()

none_explicitly_allowed = Instance(slice, allow_none=True)

also_allow_none = Slice(allow_none=True)

disallow_none = Instance(slice, allow_none=False)

also_disallow_none = Slice(allow_none=False)


class TestInstance(unittest.TestCase):
def test_explicitly_prohibit_none(self):
obj = HasSlices(disallow_none=slice(2, 5))
self.assertIsNotNone(obj.disallow_none)
with self.assertRaises(TraitError):
obj.disallow_none = None
self.assertIsNotNone(obj.disallow_none)

obj = HasSlices(also_disallow_none=slice(2, 5))
self.assertIsNotNone(obj.also_disallow_none)
with self.assertRaises(TraitError):
obj.also_disallow_none = None
self.assertIsNotNone(obj.also_disallow_none)

def test_explicitly_allow_none(self):
obj = HasSlices(none_explicitly_allowed=slice(2, 5))
self.assertIsNotNone(obj.none_explicitly_allowed)
obj.none_explicitly_allowed = None
self.assertIsNone(obj.none_explicitly_allowed)

obj = HasSlices(also_allow_none=slice(2, 5))
self.assertIsNotNone(obj.also_allow_none)
obj.also_allow_none = None
self.assertIsNone(obj.also_allow_none)

def test_allow_none_permitted_by_default(self):
obj = HasSlices(my_slice=slice(2, 5))
self.assertIsNotNone(obj.my_slice)
obj.my_slice = None
self.assertIsNone(obj.my_slice)

obj = HasSlices(also_my_slice=slice(2, 5))
self.assertIsNotNone(obj.also_my_slice)
obj.my_slice = None
self.assertIsNone(obj.my_slice)
100 changes: 100 additions & 0 deletions traits/tests/test_time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!

"""
Tests for the Time trait type.
"""

import datetime
import unittest

from traits.testing.optional_dependencies import requires_traitsui, traitsui

from traits.api import HasStrictTraits, Time, TraitError


#: Unix epoch
UNIX_EPOCH = datetime.time(12, 30)

#: Windows NT epoch
NT_EPOCH = datetime.time(18, 30)


class HasTimeTraits(HasStrictTraits):
#: Simple case - no default, no parameters, no metadata
simple_time = Time()

#: Time with default
epoch = Time(UNIX_EPOCH)

#: Time with default provided via keyword.
alternative_epoch = Time(default_value=NT_EPOCH)

#: None prohibited
none_prohibited = Time(allow_none=False)

#: None allowed
none_allowed = Time(allow_none=True)


class TestDatetime(unittest.TestCase):
def test_default(self):
obj = HasTimeTraits()
self.assertEqual(obj.simple_time, None)
self.assertEqual(obj.epoch, UNIX_EPOCH)
self.assertEqual(obj.alternative_epoch, NT_EPOCH)

def test_assign_time(self):
# By default, datetime instances are permitted.
test_time = datetime.time(6, 3, 35)
obj = HasTimeTraits()
obj.simple_time = test_time
self.assertEqual(obj.simple_time, test_time)

def test_assign_non_time(self):
obj = HasTimeTraits()
with self.assertRaises(TraitError) as exception_context:
obj.simple_time = "12:00:00"
message = str(exception_context.exception)
self.assertIn("must be a time or None, but", message)

def test_assign_datetime(self):
obj = HasTimeTraits()
with self.assertRaises(TraitError) as exception_context:
obj.simple_time = datetime.datetime(1975, 2, 13)
message = str(exception_context.exception)
self.assertIn("must be a time or None, but", message)
self.assertIsNone(obj.simple_time)

def test_assign_none(self):
obj = HasTimeTraits(simple_time=UNIX_EPOCH)
self.assertIsNotNone(obj.simple_time)
obj.simple_time = None
self.assertIsNone(obj.simple_time)

def test_allow_none_false(self):
obj = HasTimeTraits(none_prohibited=UNIX_EPOCH)
with self.assertRaises(TraitError) as exception_context:
obj.none_prohibited = None
message = str(exception_context.exception)
self.assertIn("must be a time, but", message)

def test_allow_none_true(self):
obj = HasTimeTraits(none_allowed=UNIX_EPOCH)
self.assertIsNotNone(obj.none_allowed)
obj.none_allowed = None
self.assertIsNone(obj.none_allowed)

@requires_traitsui
def test_get_editor(self):
obj = HasTimeTraits()
trait = obj.base_trait("epoch")
editor_factory = trait.get_editor()
self.assertIsInstance(editor_factory, traitsui.api.TimeEditor)
5 changes: 5 additions & 0 deletions traits/trait_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,11 @@ def clone(self, default_value=NoDefaultSpecified, **metadata):
A dictionary of metadata names and corresponding values as
arbitrary keyword arguments.

Returns
Copy link
Member Author

Choose a reason for hiding this comment

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

Drive-by documentation fix; no associated change in behaviour.

-------
clone : TraitType
Clone of self.

"""
if "parent" not in metadata:
metadata["parent"] = self
Expand Down
14 changes: 13 additions & 1 deletion traits/trait_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@
from .trait_errors import TraitError
from .trait_list_object import TraitListEvent, TraitListObject
from .trait_set_object import TraitSetEvent, TraitSetObject
from .trait_type import TraitType, _infer_default_value_type
from .trait_type import (
_infer_default_value_type,
NoDefaultSpecified,
TraitType,
)
from .traits import (
Trait,
_TraitMaker,
Expand Down Expand Up @@ -3424,6 +3428,14 @@ def get_default_value(self):

return (dvt, dv)

def clone(self, default_value=NoDefaultSpecified, **metadata):
""" pass """
mdickinson marked this conversation as resolved.
Show resolved Hide resolved
allow_none = metadata.pop("allow_none", None)
clone_of_self = super().clone(default_value=default_value, **metadata)
if allow_none is not None:
clone_of_self._allow_none = allow_none
return clone_of_self

def create_editor(self):
""" Returns the default traits UI editor for this type of trait.
"""
Expand Down