Skip to content

Commit f51efb6

Browse files
committed
Optional Trait - Docs, linting, and another test
1 parent 98a97c5 commit f51efb6

File tree

5 files changed

+87
-4
lines changed

5 files changed

+87
-4
lines changed

docs/source/traits_api_reference/trait_types.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@ Traits
241241
.. autoclass:: Union
242242
:show-inheritance:
243243

244+
.. autoclass:: Optional
245+
:show-inheritance:
246+
244247
.. autoclass:: Either
245248
:show-inheritance:
246249

docs/source/traits_user_manual/defining.rst

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ the table.
265265
.. index:: Directory(), Disallow, Either(), Enum()
266266
.. index:: Event(), Expression(), false, File()
267267
.. index:: Instance(), List(), Method(), Module()
268-
.. index:: Password(), Property(), Python()
268+
.. index:: Optional(), Password(), Property(), Python()
269269
.. index:: PythonValue(), Range(), ReadOnly(), Regex()
270270
.. index:: Set() String(), This, Time()
271271
.. index:: ToolbarButton(), true, Tuple(), Type()
@@ -355,6 +355,8 @@ the table.
355355
+------------------+----------------------------------------------------------+
356356
| Module | Module([\*\*\ *metadata*]) |
357357
+------------------+----------------------------------------------------------+
358+
| Optional | Optional(*trait*\ [, \*\*\ *metadata*]) |
359+
+------------------+----------------------------------------------------------+
358360
| Password | Password([*value* = '', *minlen* = 0, *maxlen* = |
359361
| | sys.maxsize, *regex* = '', \*\*\ *metadata*]) |
360362
+------------------+----------------------------------------------------------+
@@ -700,6 +702,45 @@ The following example illustrates the difference between `Either` and `Union`::
700702
... primes = Union([2], None, {'3':6}, 5, 7, 11)
701703
ValueError: Union trait declaration expects a trait type or an instance of trait type or None, but got [2] instead
702704

705+
.. index:: Optional trait
706+
707+
.. _optional:
708+
709+
Optional
710+
::::::::
711+
The Optional trait is a shorthand for ``Union(None, *trait*)``. It allows
712+
the value of the trait to be either None or a specified type. The default
713+
value of the trait is None unless specified by ``default_value``.
714+
715+
.. index::
716+
pair: Optional trait; examples
717+
718+
The following is an example of using Optional::
719+
720+
# optional.py --- Example of Optional predefined trait
721+
722+
from traits.api import HasTraits, Optional, Str
723+
724+
class Person(HasTraits):
725+
name = Str
726+
nickname = Optional(Str)
727+
728+
This example defines a ``Person`` with a ``name`` and an optional ``nickname``.
729+
Their ``nickname`` can be ``None`` or a string. For example::
730+
731+
>>> from traits.api import HasTraits, Optional, Str
732+
>>> class Person(HasTraits):
733+
... name = Str
734+
... nickname = Optional(Str)
735+
...
736+
>>> joseph = Person(name="Joseph")
737+
>>> # Joseph has no nickname
738+
>>> joseph.nickname is None
739+
True
740+
>>> joseph.nickname = "Joe"
741+
>>> joseph.nickname
742+
'Joe'
743+
703744
.. index:: Either trait
704745

705746
.. _either:

traits/api.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ from .trait_types import (
112112
ToolbarButton as ToolbarButton,
113113
Either as Either,
114114
Union as Union,
115+
Optional as Optional,
115116
Type as Type,
116117
Subclass as Subclass,
117118
Symbol as Symbol,

traits/tests/test_optional.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import unittest
1212

1313
from traits.api import (
14-
Bytes,
1514
DefaultValue,
1615
Float,
1716
HasTraits,
@@ -21,8 +20,8 @@
2120
Str,
2221
TraitError,
2322
TraitType,
24-
Type,
2523
Optional,
24+
Union,
2625
Constant,
2726
)
2827
from traits.trait_types import _NoneTrait
@@ -234,6 +233,45 @@ def _attribute_changed(self, new):
234233
obj.attribute = None
235234
self.assertIsNone(obj.shadow_attribute)
236235

236+
def test_optional_nested(self):
237+
"""
238+
You can nest ``Optional``... if you want to
239+
"""
240+
241+
class TestClass(HasTraits):
242+
attribute = Optional(Optional(Int))
243+
244+
self.assertIsNone(TestClass(attribute=None).attribute)
245+
self.assertIsNone(TestClass().attribute)
246+
247+
obj = TestClass(attribute=3)
248+
249+
obj.attribute = 5
250+
self.assertEqual(obj.attribute, 5)
251+
252+
obj.attribute = None
253+
self.assertIsNone(obj.attribute)
254+
255+
def test_optional_union_of_optional(self):
256+
"""
257+
``Union(T1, Optional(T2))`` acts like ``Union(T1, None, T2)``
258+
"""
259+
class TestClass(HasTraits):
260+
attribute = Union(Int, Optional(Float))
261+
262+
self.assertEqual(TestClass(attribute=3).attribute, 3)
263+
self.assertEqual(TestClass(attribute=3.0).attribute, 3.0)
264+
self.assertIsNone(TestClass(attribute=None).attribute)
265+
self.assertEqual(TestClass().attribute, 0)
266+
267+
a = TestClass(attribute=3)
268+
a.attribute = 5
269+
self.assertEqual(a.attribute, 5)
270+
a.attribute = 5.0
271+
self.assertEqual(a.attribute, 5.0)
272+
a.attribute = None
273+
self.assertIsNone(a.attribute)
274+
237275
def test_optional_extend_trait(self):
238276
class OptionalOrStr(Optional):
239277
def validate(self, obj, name, value):

traits/trait_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4297,7 +4297,7 @@ class Optional(Union):
42974297
trait : a trait or value that can be converted using trait_from()
42984298
The type of item that the set contains. Must not be ``None``.
42994299
"""
4300-
def __init__(self, trait, **metadata):
4300+
def __init__(self, trait, **metadata):
43014301
if trait is None:
43024302
raise TraitError("Optional type must not be None.")
43034303

0 commit comments

Comments
 (0)